@objectstack/runtime 7.9.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -188,7 +188,7 @@ var init_seed_loader = __esm({
188
188
  const config = request.config;
189
189
  const allErrors = [];
190
190
  const allResults = [];
191
- const datasets = this.filterByEnv(request.datasets, config.env);
191
+ const datasets = this.filterByEnv(request.seeds, config.env);
192
192
  if (datasets.length === 0) {
193
193
  return this.buildEmptyResult(config, Date.now() - startTime);
194
194
  }
@@ -258,10 +258,10 @@ var init_seed_loader = __esm({
258
258
  }
259
259
  async validate(datasets, config) {
260
260
  const parsedConfig = import_data.SeedLoaderConfigSchema.parse({ ...config, dryRun: true });
261
- return this.load({ datasets, config: parsedConfig });
261
+ return this.load({ seeds: datasets, config: parsedConfig });
262
262
  }
263
263
  // ==========================================================================
264
- // Internal: Dataset Loading
264
+ // Internal: Seed Loading
265
265
  // ==========================================================================
266
266
  async loadDataset(dataset, config, refMap, insertedRecords, deferredUpdates, allErrors) {
267
267
  const objectName = dataset.object;
@@ -1680,7 +1680,7 @@ var init_app_plugin = __esm({
1680
1680
  const seedLoader = new SeedLoaderService(ql, md, loggerRef);
1681
1681
  const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
1682
1682
  const request = SeedLoaderRequestSchema.parse({
1683
- datasets: datasetsNow,
1683
+ seeds: datasetsNow,
1684
1684
  config: {
1685
1685
  defaultMode: "upsert",
1686
1686
  multiPass: true,
@@ -1700,7 +1700,7 @@ var init_app_plugin = __esm({
1700
1700
  };
1701
1701
  };
1702
1702
  registerSvc("seed-replayer", replayer);
1703
- ctx.logger.info(`[Seeder] Registered ${normalizedDatasets.length} datasets + replayer on kernel (total datasets: ${merged.length})`);
1703
+ ctx.logger.info(`[Seeder] Registered ${normalizedDatasets.length} datasets + replayer on kernel (total seeds: ${merged.length})`);
1704
1704
  } catch (e) {
1705
1705
  ctx.logger.warn("[Seeder] Failed to register seed-datasets/seed-replayer service", { error: e?.message });
1706
1706
  }
@@ -1716,7 +1716,7 @@ var init_app_plugin = __esm({
1716
1716
  const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
1717
1717
  const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
1718
1718
  const request = SeedLoaderRequestSchema.parse({
1719
- datasets: normalizedDatasets,
1719
+ seeds: normalizedDatasets,
1720
1720
  config: { defaultMode: "upsert", multiPass: true, identity: seedIdentity }
1721
1721
  });
1722
1722
  const result = await seedLoader.load(request);
@@ -2190,170 +2190,18 @@ var init_standalone_stack = __esm({
2190
2190
  }
2191
2191
  });
2192
2192
 
2193
- // src/cloud/environment-org-seed.ts
2194
- var environment_org_seed_exports = {};
2195
- __export(environment_org_seed_exports, {
2196
- seedProjectMember: () => seedProjectMember,
2197
- seedProjectOrganization: () => seedProjectOrganization
2198
- });
2199
- async function seedProjectOrganization(kernel, seed, logger) {
2200
- if (!seed?.id || !seed?.name) return "skipped";
2201
- try {
2202
- const ql = kernel.getService("objectql");
2203
- if (!ql?.insert || !ql?.find) {
2204
- logger?.warn?.("[seedProjectOrganization] objectql service unavailable", { orgId: seed.id });
2205
- return "skipped";
2206
- }
2207
- try {
2208
- const existing = await ql.find(SYS_ORG, { where: { id: seed.id } });
2209
- const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
2210
- if (Array.isArray(rows) && rows.length > 0) return "exists";
2211
- } catch {
2212
- }
2213
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2214
- await ql.insert(SYS_ORG, {
2215
- id: seed.id,
2216
- name: seed.name,
2217
- slug: seed.slug ?? null,
2218
- logo: seed.logo ?? null,
2219
- metadata: null,
2220
- created_at: nowIso
2221
- });
2222
- logger?.info?.("[seedProjectOrganization] org seeded", {
2223
- orgId: seed.id,
2224
- name: seed.name
2225
- });
2226
- return "inserted";
2227
- } catch (err) {
2228
- logger?.warn?.("[seedProjectOrganization] failed (non-fatal)", {
2229
- orgId: seed.id,
2230
- error: err?.message
2231
- });
2232
- return "error";
2233
- }
2234
- }
2235
- async function seedProjectMember(kernel, args, logger) {
2236
- const { userId, organizationId } = args;
2237
- const role = args.role ?? "member";
2238
- if (!userId || !organizationId) return "skipped";
2239
- try {
2240
- const ql = kernel.getService("objectql");
2241
- if (!ql?.insert || !ql?.find) {
2242
- logger?.warn?.("[seedProjectMember] objectql service unavailable", { userId, organizationId });
2243
- return "skipped";
2244
- }
2245
- try {
2246
- const existing = await ql.find("sys_member", {
2247
- where: { user_id: userId, organization_id: organizationId }
2248
- });
2249
- const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
2250
- if (Array.isArray(rows) && rows.length > 0) return "exists";
2251
- } catch {
2252
- }
2253
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2254
- const memId = `mem_${Math.random().toString(36).slice(2, 14)}`;
2255
- await ql.insert("sys_member", {
2256
- id: memId,
2257
- organization_id: organizationId,
2258
- user_id: userId,
2259
- role,
2260
- created_at: nowIso
2261
- });
2262
- logger?.info?.("[seedProjectMember] member seeded", {
2263
- userId,
2264
- organizationId,
2265
- role
2266
- });
2267
- return "inserted";
2268
- } catch (err) {
2269
- logger?.warn?.("[seedProjectMember] failed (non-fatal)", {
2270
- userId,
2271
- organizationId,
2272
- error: err?.message
2273
- });
2274
- return "error";
2275
- }
2276
- }
2277
- var SYS_ORG;
2278
- var init_environment_org_seed = __esm({
2279
- "src/cloud/environment-org-seed.ts"() {
2280
- "use strict";
2281
- SYS_ORG = "sys_organization";
2282
- }
2283
- });
2284
-
2285
- // src/cloud/environment-owner-seed.ts
2286
- var environment_owner_seed_exports = {};
2287
- __export(environment_owner_seed_exports, {
2288
- seedProjectOwner: () => seedProjectOwner
2289
- });
2290
- async function seedProjectOwner(kernel, seed, logger) {
2291
- if (!seed?.userId || !seed?.email) return "skipped";
2292
- try {
2293
- const ql = kernel.getService("objectql");
2294
- if (!ql?.insert || !ql?.find) {
2295
- logger?.warn?.("[seedProjectOwner] objectql service unavailable", { userId: seed.userId });
2296
- return "skipped";
2297
- }
2298
- try {
2299
- const existing = await ql.find(SYS_USER, { where: { id: seed.userId } });
2300
- const rows = Array.isArray(existing) ? existing : existing?.value ?? [];
2301
- if (Array.isArray(rows) && rows.length > 0) return "exists";
2302
- } catch {
2303
- }
2304
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2305
- await ql.insert(SYS_USER, {
2306
- id: seed.userId,
2307
- email: seed.email,
2308
- name: seed.name ?? seed.email.split("@")[0] ?? "Owner",
2309
- image: seed.image ?? null,
2310
- // Cloud already verified the upstream email. Marking it verified
2311
- // here is what unblocks better-auth's accountLinking check on
2312
- // the first SSO callback (alongside the trustedProviders config
2313
- // in plugin-auth/auth-manager.ts).
2314
- email_verified: true,
2315
- created_at: nowIso,
2316
- updated_at: nowIso
2317
- });
2318
- logger?.info?.("[seedProjectOwner] owner seeded", {
2319
- userId: seed.userId,
2320
- email: seed.email
2321
- });
2322
- return "inserted";
2323
- } catch (err) {
2324
- logger?.warn?.("[seedProjectOwner] failed (non-fatal)", {
2325
- userId: seed.userId,
2326
- error: err?.message
2327
- });
2328
- return "error";
2329
- }
2330
- }
2331
- var SYS_USER;
2332
- var init_environment_owner_seed = __esm({
2333
- "src/cloud/environment-owner-seed.ts"() {
2334
- "use strict";
2335
- SYS_USER = "sys_user";
2336
- }
2337
- });
2338
-
2339
2193
  // src/index.ts
2340
2194
  var index_exports = {};
2341
2195
  __export(index_exports, {
2342
2196
  AppPlugin: () => AppPlugin,
2343
- ArtifactApiClient: () => ArtifactApiClient,
2344
- ArtifactEnvironmentRegistry: () => ArtifactEnvironmentRegistry,
2345
- ArtifactKernelFactory: () => ArtifactKernelFactory,
2346
- AuthProxyPlugin: () => AuthProxyPlugin,
2347
2197
  DEFAULT_CLOUD_URL: () => DEFAULT_CLOUD_URL,
2348
2198
  DEFAULT_RATE_LIMITS: () => DEFAULT_RATE_LIMITS,
2349
2199
  DriverPlugin: () => DriverPlugin,
2350
2200
  ExternalValidationPlugin: () => ExternalValidationPlugin,
2351
- FileArtifactApiClient: () => FileArtifactApiClient,
2352
2201
  HttpDispatcher: () => HttpDispatcher,
2353
2202
  HttpServer: () => HttpServer,
2354
2203
  InMemoryErrorReporter: () => import_observability2.InMemoryErrorReporter,
2355
2204
  InMemoryMetricsRegistry: () => import_observability.InMemoryMetricsRegistry,
2356
- KernelManager: () => KernelManager,
2357
2205
  MarketplaceInstallLocalPlugin: () => MarketplaceInstallLocalPlugin,
2358
2206
  MarketplaceProxyPlugin: () => MarketplaceProxyPlugin,
2359
2207
  MiddlewareManager: () => MiddlewareManager,
@@ -2363,7 +2211,6 @@ __export(index_exports, {
2363
2211
  OBSERVABILITY_METRICS_SERVICE: () => import_observability3.OBSERVABILITY_METRICS_SERVICE,
2364
2212
  ObjectKernel: () => import_core4.ObjectKernel,
2365
2213
  ObservabilityServicePlugin: () => ObservabilityServicePlugin,
2366
- PLATFORM_SSO_PROVIDER_ID: () => PLATFORM_SSO_PROVIDER_ID,
2367
2214
  QuickJSScriptRunner: () => QuickJSScriptRunner,
2368
2215
  RUNTIME_METRICS: () => import_observability.RUNTIME_METRICS,
2369
2216
  RateLimiter: () => RateLimiter,
@@ -2376,10 +2223,8 @@ __export(index_exports, {
2376
2223
  SandboxError: () => SandboxError,
2377
2224
  SeedLoaderService: () => SeedLoaderService,
2378
2225
  UnimplementedScriptRunner: () => UnimplementedScriptRunner,
2379
- _resetEnvDeprecationWarnings: () => import_types5._resetEnvDeprecationWarnings,
2226
+ _resetEnvDeprecationWarnings: () => import_types4._resetEnvDeprecationWarnings,
2380
2227
  actionBodyRunnerFactory: () => actionBodyRunnerFactory,
2381
- backfillPlatformSsoClients: () => backfillPlatformSsoClients,
2382
- buildPlatformSsoRedirectUri: () => buildPlatformSsoRedirectUri,
2383
2228
  buildSecurityHeaders: () => buildSecurityHeaders,
2384
2229
  collectBundleActions: () => collectBundleActions,
2385
2230
  collectBundleFunctions: () => collectBundleFunctions,
@@ -2387,12 +2232,9 @@ __export(index_exports, {
2387
2232
  createDefaultHostConfig: () => createDefaultHostConfig,
2388
2233
  createDispatcherPlugin: () => createDispatcherPlugin,
2389
2234
  createExternalValidationPlugin: () => createExternalValidationPlugin,
2390
- createObjectOSStack: () => createObjectOSStack,
2391
2235
  createRestApiPlugin: () => import_rest.createRestApiPlugin,
2392
2236
  createStandaloneStack: () => createStandaloneStack,
2393
2237
  createSystemEnvironmentPlugin: () => createSystemEnvironmentPlugin,
2394
- derivePlatformSsoClientId: () => derivePlatformSsoClientId,
2395
- derivePlatformSsoClientSecret: () => derivePlatformSsoClientSecret,
2396
2238
  extractRequestId: () => extractRequestId,
2397
2239
  formatTraceparent: () => formatTraceparent,
2398
2240
  generateRequestId: () => generateRequestId,
@@ -2402,14 +2244,13 @@ __export(index_exports, {
2402
2244
  mergeRuntimeModule: () => mergeRuntimeModule,
2403
2245
  parseTraceparent: () => parseTraceparent,
2404
2246
  readArtifactSource: () => readArtifactSource,
2405
- readEnvWithDeprecation: () => import_types5.readEnvWithDeprecation,
2247
+ readEnvWithDeprecation: () => import_types4.readEnvWithDeprecation,
2406
2248
  resolveCloudUrl: () => resolveCloudUrl,
2407
2249
  resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
2408
2250
  resolveErrorReporter: () => resolveErrorReporter,
2409
2251
  resolveMetrics: () => resolveMetrics,
2410
2252
  resolveObjectStackHome: () => resolveObjectStackHome,
2411
- resolveRequestId: () => resolveRequestId,
2412
- seedPlatformSsoClient: () => seedPlatformSsoClient
2253
+ resolveRequestId: () => resolveRequestId
2413
2254
  });
2414
2255
  module.exports = __toCommonJS(index_exports);
2415
2256
  var import_core4 = require("@objectstack/core");
@@ -2684,35 +2525,15 @@ function safeGet(ctx, name) {
2684
2525
  }
2685
2526
 
2686
2527
  // src/http-dispatcher.ts
2687
- var import_core2 = require("@objectstack/core");
2528
+ var import_core3 = require("@objectstack/core");
2688
2529
  var import_system2 = require("@objectstack/spec/system");
2689
2530
  var import_shared2 = require("@objectstack/spec/shared");
2690
2531
  init_package_state_store();
2691
2532
 
2533
+ // src/security/api-key.ts
2534
+ var import_core2 = require("@objectstack/core");
2535
+
2692
2536
  // src/security/resolve-execution-context.ts
2693
- function readHeader(headers, name) {
2694
- if (!headers) return void 0;
2695
- const lower = name.toLowerCase();
2696
- if (typeof headers.get === "function") {
2697
- const v = headers.get(name) ?? headers.get(lower);
2698
- return v == null ? void 0 : String(v);
2699
- }
2700
- for (const key of Object.keys(headers)) {
2701
- if (key.toLowerCase() === lower) {
2702
- const v = headers[key];
2703
- return Array.isArray(v) ? v[0] : v == null ? void 0 : String(v);
2704
- }
2705
- }
2706
- return void 0;
2707
- }
2708
- function extractApiKey(headers) {
2709
- const x = readHeader(headers, "x-api-key");
2710
- if (x) return x.trim();
2711
- const auth = readHeader(headers, "authorization");
2712
- if (!auth) return void 0;
2713
- const m = auth.match(/^ApiKey\s+(.+)$/i);
2714
- return m ? m[1].trim() : void 0;
2715
- }
2716
2537
  function toHeaders(input) {
2717
2538
  if (!input) return new Headers();
2718
2539
  if (typeof Headers !== "undefined" && input instanceof Headers) return input;
@@ -2755,34 +2576,12 @@ async function resolveExecutionContext(opts) {
2755
2576
  };
2756
2577
  let userId;
2757
2578
  let tenantId;
2758
- const apiKey = extractApiKey(headers);
2759
- if (apiKey) {
2760
- try {
2761
- const authService = await opts.getService("auth");
2762
- const verify = authService?.api?.verifyApiKey ?? authService?.api?.apiKey?.verify;
2763
- if (typeof verify === "function") {
2764
- const res = await verify({ body: { key: apiKey } });
2765
- const payload = res?.key ?? res;
2766
- if (payload?.userId) userId = payload.userId;
2767
- if (payload?.organizationId) tenantId = payload.organizationId;
2768
- if (Array.isArray(payload?.permissions)) {
2769
- ctx.permissions.push(...payload.permissions);
2770
- }
2771
- if (Array.isArray(payload?.scopes)) {
2772
- ctx.permissions.push(...payload.scopes);
2773
- }
2774
- }
2775
- } catch {
2776
- }
2777
- if (!userId) {
2778
- const ql2 = await opts.getQl();
2779
- const rows = await tryFind(ql2, "sys_api_key", { key: apiKey, active: true }, 1);
2780
- const row = rows[0];
2781
- if (row) {
2782
- userId = row.user_id ?? row.userId;
2783
- tenantId = row.organization_id ?? row.organizationId;
2784
- if (Array.isArray(row.scopes)) ctx.permissions.push(...row.scopes);
2785
- }
2579
+ const keyPrincipal = await (0, import_core2.resolveApiKeyPrincipal)(await opts.getQl(), headers);
2580
+ if (keyPrincipal) {
2581
+ userId = keyPrincipal.userId;
2582
+ tenantId = keyPrincipal.tenantId;
2583
+ for (const scope of keyPrincipal.scopes) {
2584
+ if (!ctx.permissions.includes(scope)) ctx.permissions.push(scope);
2786
2585
  }
2787
2586
  }
2788
2587
  if (!userId) {
@@ -3058,6 +2857,253 @@ var _HttpDispatcher = class _HttpDispatcher {
3058
2857
  }
3059
2858
  throw { statusCode: 400, message: `Unknown data action: ${action}` };
3060
2859
  }
2860
+ /**
2861
+ * Handle an MCP request over the Streamable HTTP transport (`/mcp`).
2862
+ *
2863
+ * Gating + auth (fail-closed):
2864
+ * - **opt-in**: only served when `OS_MCP_SERVER_ENABLED=true` (single-env
2865
+ * runtime). Multi-tenant cloud overrides this gate per env. When off we
2866
+ * return 404 so the surface isn't advertised.
2867
+ * - **auth**: requires a principal already resolved by
2868
+ * `resolveExecutionContext` (the `sys_api_key` Bearer/header path or a
2869
+ * session). Anonymous → 401.
2870
+ *
2871
+ * Execution: the MCP runtime builds a stateless per-request server whose
2872
+ * object-CRUD tools run through {@link callData} bound to THIS request's
2873
+ * ExecutionContext — i.e. the exact permission + RLS path the REST API
2874
+ * uses. An external agent can never exceed the key's authority.
2875
+ */
2876
+ async handleMcp(body, context) {
2877
+ if (!_HttpDispatcher.isMcpEnabled()) {
2878
+ return { handled: true, response: this.error("MCP server is not enabled for this environment", 404) };
2879
+ }
2880
+ const mcp = await this.resolveService("mcp", context.environmentId);
2881
+ if (!mcp || typeof mcp.handleHttpRequest !== "function") {
2882
+ return { handled: true, response: this.error("MCP server is not available", 501) };
2883
+ }
2884
+ const ec = context.executionContext;
2885
+ if (!ec || !ec.userId && !ec.isSystem) {
2886
+ return { handled: true, response: this.error("Unauthorized: a valid API key is required", 401) };
2887
+ }
2888
+ const webRequest = this.toMcpWebRequest(context.request, body);
2889
+ if (!webRequest) {
2890
+ return { handled: true, response: this.error("MCP transport requires a standard HTTP request", 400) };
2891
+ }
2892
+ const bridge = this.buildMcpBridge(context);
2893
+ let webRes;
2894
+ try {
2895
+ webRes = await mcp.handleHttpRequest(webRequest, { bridge, parsedBody: body });
2896
+ } catch (err) {
2897
+ return { handled: true, response: this.error(err?.message ?? "MCP request failed", 500) };
2898
+ }
2899
+ const headers = {};
2900
+ try {
2901
+ webRes.headers.forEach((v, k) => {
2902
+ headers[k] = v;
2903
+ });
2904
+ } catch {
2905
+ }
2906
+ const text = await webRes.text().catch(() => "");
2907
+ let responseBody = null;
2908
+ if (text) {
2909
+ const ct = headers["content-type"] ?? "";
2910
+ if (ct.includes("application/json")) {
2911
+ try {
2912
+ responseBody = JSON.parse(text);
2913
+ } catch {
2914
+ responseBody = text;
2915
+ }
2916
+ } else {
2917
+ responseBody = text;
2918
+ }
2919
+ }
2920
+ return { handled: true, response: { status: webRes.status, headers, body: responseBody } };
2921
+ }
2922
+ /** Whether the MCP HTTP surface is opted in for this single-env runtime. */
2923
+ static isMcpEnabled() {
2924
+ return typeof process !== "undefined" && process.env?.OS_MCP_SERVER_ENABLED === "true";
2925
+ }
2926
+ /**
2927
+ * Normalise the inbound request into a Web-standard `Request` for the MCP
2928
+ * transport. Accepts an already-Web `Request`, or a node/Hono-style req
2929
+ * (plain `headers` object, path-only `url`). Returns undefined only if the
2930
+ * shape is unusable. The body is carried separately via `parsedBody`, so a
2931
+ * GET/DELETE (no body) and a POST (JSON-RPC) both normalise cleanly.
2932
+ */
2933
+ toMcpWebRequest(raw, parsedBody) {
2934
+ if (!raw) return void 0;
2935
+ if (typeof raw.headers?.get === "function" && typeof raw.url === "string" && typeof raw.method === "string") {
2936
+ return raw;
2937
+ }
2938
+ try {
2939
+ const method = String(raw.method ?? "POST").toUpperCase();
2940
+ const headers = new Headers();
2941
+ const h = raw.headers;
2942
+ if (h) {
2943
+ if (typeof h.forEach === "function") {
2944
+ h.forEach((v, k) => {
2945
+ if (v != null) headers.set(String(k), String(v));
2946
+ });
2947
+ } else {
2948
+ for (const k of Object.keys(h)) {
2949
+ const v = h[k];
2950
+ if (v != null) headers.set(k, Array.isArray(v) ? v.join(",") : String(v));
2951
+ }
2952
+ }
2953
+ }
2954
+ let url;
2955
+ try {
2956
+ url = new URL(String(raw.url)).toString();
2957
+ } catch {
2958
+ const host = headers.get("host") || "mcp.local";
2959
+ const path = typeof raw.url === "string" && raw.url ? raw.url : "/api/v1/mcp";
2960
+ url = `https://${host}${path.startsWith("/") ? path : `/${path}`}`;
2961
+ }
2962
+ const init = { method, headers };
2963
+ if (method !== "GET" && method !== "HEAD" && method !== "DELETE") {
2964
+ init.body = typeof parsedBody === "string" ? parsedBody : JSON.stringify(parsedBody ?? {});
2965
+ }
2966
+ return new Request(url, init);
2967
+ } catch {
2968
+ return void 0;
2969
+ }
2970
+ }
2971
+ /**
2972
+ * Build a principal-bound {@link McpDataBridge}: every method runs AS the
2973
+ * request's ExecutionContext through {@link callData} (RLS/permissions) and
2974
+ * the per-env metadata service. Keeps the MCP tool layer free of any direct
2975
+ * engine access.
2976
+ */
2977
+ buildMcpBridge(context) {
2978
+ const ec = context.executionContext;
2979
+ const envId = context.environmentId;
2980
+ const driver = context.dataDriver;
2981
+ const callData = this.callData.bind(this);
2982
+ const getMeta = () => this.resolveService("metadata", envId);
2983
+ return {
2984
+ listObjects: async () => {
2985
+ const meta = await getMeta();
2986
+ const objs = await meta?.listObjects?.() ?? [];
2987
+ return objs.map((o) => ({
2988
+ name: o.name,
2989
+ label: o.label ?? o.name,
2990
+ fieldCount: o.fields ? Object.keys(o.fields).length : void 0
2991
+ }));
2992
+ },
2993
+ describeObject: async (name) => {
2994
+ const meta = await getMeta();
2995
+ const def = await meta?.getObject?.(name);
2996
+ if (!def) return null;
2997
+ const fields = def.fields ?? {};
2998
+ return {
2999
+ name: def.name,
3000
+ label: def.label ?? def.name,
3001
+ fields: Object.entries(fields).map(([k, f]) => ({
3002
+ name: k,
3003
+ type: f?.type,
3004
+ label: f?.label ?? k,
3005
+ required: f?.required ?? false
3006
+ })),
3007
+ enableFeatures: def.enable ?? {}
3008
+ };
3009
+ },
3010
+ query: async (object, o) => {
3011
+ const query = {};
3012
+ if (o?.where) query.where = o.where;
3013
+ if (o?.fields) query.fields = o.fields;
3014
+ if (typeof o?.limit === "number") query.limit = o.limit;
3015
+ if (typeof o?.offset === "number") query.offset = o.offset;
3016
+ if (o?.orderBy) query.orderBy = o.orderBy;
3017
+ return await callData("query", { object, query }, driver, envId, ec);
3018
+ },
3019
+ get: async (object, id) => {
3020
+ const res = await callData("get", { object, id }, driver, envId, ec);
3021
+ return res?.record ?? res ?? null;
3022
+ },
3023
+ create: async (object, data) => await callData("create", { object, data }, driver, envId, ec),
3024
+ update: async (object, id, data) => await callData("update", { object, id, data }, driver, envId, ec),
3025
+ remove: async (object, id) => await callData("delete", { object, id }, driver, envId, ec)
3026
+ };
3027
+ }
3028
+ /**
3029
+ * Generate a `sys_api_key` and return the raw secret EXACTLY ONCE
3030
+ * (`POST /keys`). This is the only mint path — the raw key is never stored
3031
+ * (only its sha256 hash) and never re-displayable.
3032
+ *
3033
+ * Security (zero-tolerance):
3034
+ * - Requires an authenticated principal; `user_id` is PINNED to that
3035
+ * caller and is NEVER read from the request body (no impersonation).
3036
+ * - Body is whitelisted to `name` (+ optional `expires_at`); any
3037
+ * `key` / `id` / `user_id` / `revoked` in the body is ignored, so a
3038
+ * caller cannot forge a known-secret or escalate.
3039
+ * - `scopes` are intentionally NOT accepted from the body in v1: the
3040
+ * verify path ADDS scopes to the principal's permissions, so honouring
3041
+ * arbitrary body scopes would be an escalation vector. A generated key
3042
+ * therefore acts exactly AS the caller (via `user_id` resolution).
3043
+ * Narrowing/scoped keys need subset-enforcement — deferred.
3044
+ * - The raw key and its hash never enter logs or error messages.
3045
+ * - The row is written with an elevated `{ isSystem: true }` context
3046
+ * because `sys_api_key` is protection-locked; safe because the row's
3047
+ * contents are fully server-controlled (user_id pinned to caller).
3048
+ */
3049
+ async handleKeys(method, body, context) {
3050
+ if (method !== "POST") {
3051
+ return { handled: true, response: this.error("Method not allowed", 405) };
3052
+ }
3053
+ const ec = context.executionContext;
3054
+ if (!ec || !ec.userId) {
3055
+ return { handled: true, response: this.error("Unauthorized: sign in to generate an API key", 401) };
3056
+ }
3057
+ const rawName = typeof body?.name === "string" ? body.name.trim() : "";
3058
+ const name = rawName || "API Key";
3059
+ let expiresAt;
3060
+ if (body?.expires_at != null && body.expires_at !== "") {
3061
+ const ms = typeof body.expires_at === "number" ? body.expires_at < 1e12 ? body.expires_at * 1e3 : body.expires_at : Date.parse(String(body.expires_at));
3062
+ if (Number.isNaN(ms)) {
3063
+ return { handled: true, response: this.error("Invalid expires_at: must be a parseable date", 400) };
3064
+ }
3065
+ if (ms <= Date.now()) {
3066
+ return { handled: true, response: this.error("Invalid expires_at: must be in the future", 400) };
3067
+ }
3068
+ expiresAt = new Date(ms).toISOString();
3069
+ }
3070
+ const ql = await this.getObjectQLService(context.environmentId) ?? await this.resolveService("objectql", context.environmentId);
3071
+ if (!ql || typeof ql.insert !== "function") {
3072
+ return { handled: true, response: this.error("Data service not available", 503) };
3073
+ }
3074
+ const generated = (0, import_core2.generateApiKey)();
3075
+ const row = {
3076
+ name,
3077
+ key: generated.hash,
3078
+ prefix: generated.prefix,
3079
+ user_id: ec.userId,
3080
+ revoked: false
3081
+ };
3082
+ if (expiresAt) row.expires_at = expiresAt;
3083
+ let inserted;
3084
+ try {
3085
+ inserted = await ql.insert("sys_api_key", row, { context: { isSystem: true } });
3086
+ } catch {
3087
+ return { handled: true, response: this.error("Failed to create API key", 500) };
3088
+ }
3089
+ const id = inserted?.id ?? (Array.isArray(inserted) ? inserted[0]?.id : void 0);
3090
+ return {
3091
+ handled: true,
3092
+ response: {
3093
+ status: 201,
3094
+ body: {
3095
+ success: true,
3096
+ data: {
3097
+ id,
3098
+ name,
3099
+ prefix: generated.prefix,
3100
+ key: generated.raw,
3101
+ ...expiresAt ? { expires_at: expiresAt } : {}
3102
+ }
3103
+ }
3104
+ }
3105
+ };
3106
+ }
3061
3107
  /**
3062
3108
  * Parse a project UUID out of a scoped URL path such as
3063
3109
  * `/api/v1/environments/abc-123/data/task` or `/projects/abc-123/meta`.
@@ -3344,7 +3390,11 @@ var _HttpDispatcher = class _HttpDispatcher {
3344
3390
  realtime: hasWebSockets ? `${prefix}/realtime` : void 0,
3345
3391
  notifications: hasNotification ? `${prefix}/notifications` : void 0,
3346
3392
  ai: hasAi ? `${prefix}/ai` : void 0,
3347
- i18n: hasI18n ? `${prefix}/i18n` : void 0
3393
+ i18n: hasI18n ? `${prefix}/i18n` : void 0,
3394
+ // MCP (Streamable HTTP) is opt-in per env — only advertised
3395
+ // when OS_MCP_SERVER_ENABLED=true so the surface isn't exposed
3396
+ // by default. The objectui Integrations page reads this.
3397
+ mcp: _HttpDispatcher.isMcpEnabled() ? `${prefix}/mcp` : void 0
3348
3398
  };
3349
3399
  const svcAvailable = (route, provider) => ({
3350
3400
  enabled: true,
@@ -3372,7 +3422,7 @@ var _HttpDispatcher = class _HttpDispatcher {
3372
3422
  return {
3373
3423
  name: "ObjectOS",
3374
3424
  version: "1.0.0",
3375
- environment: (0, import_core2.getEnv)("NODE_ENV", "development"),
3425
+ environment: (0, import_core3.getEnv)("NODE_ENV", "development"),
3376
3426
  routes,
3377
3427
  endpoints: routes,
3378
3428
  // Alias for backward compatibility with some clients
@@ -3877,7 +3927,7 @@ var _HttpDispatcher = class _HttpDispatcher {
3877
3927
  let translations = i18nService.getTranslations(locale);
3878
3928
  if (Object.keys(translations).length === 0) {
3879
3929
  const availableLocales = typeof i18nService.getLocales === "function" ? i18nService.getLocales() : [];
3880
- const resolved = (0, import_core2.resolveLocale)(locale, availableLocales);
3930
+ const resolved = (0, import_core3.resolveLocale)(locale, availableLocales);
3881
3931
  if (resolved && resolved !== locale) {
3882
3932
  translations = i18nService.getTranslations(resolved);
3883
3933
  return { handled: true, response: this.success({ locale: resolved, requestedLocale: locale, translations }) };
@@ -3890,7 +3940,7 @@ var _HttpDispatcher = class _HttpDispatcher {
3890
3940
  let locale = parts[2] ? decodeURIComponent(parts[2]) : query?.locale;
3891
3941
  if (!locale) return { handled: true, response: this.error("Missing locale parameter", 400) };
3892
3942
  const availableLocales = typeof i18nService.getLocales === "function" ? i18nService.getLocales() : [];
3893
- const resolved = (0, import_core2.resolveLocale)(locale, availableLocales);
3943
+ const resolved = (0, import_core3.resolveLocale)(locale, availableLocales);
3894
3944
  if (resolved) locale = resolved;
3895
3945
  if (typeof i18nService.getFieldLabels === "function") {
3896
3946
  const labels2 = i18nService.getFieldLabels(objectName, locale);
@@ -3998,6 +4048,18 @@ var _HttpDispatcher = class _HttpDispatcher {
3998
4048
  ...organizationId ? { organizationId } : {},
3999
4049
  ...body?.actor ? { actor: body.actor } : {}
4000
4050
  });
4051
+ try {
4052
+ const seedNames = (result?.published ?? []).filter((p) => p?.type === "seed").map((p) => p.name);
4053
+ if (seedNames.length > 0) {
4054
+ result.seedApplied = await this.applyPublishedSeeds(
4055
+ seedNames,
4056
+ organizationId,
4057
+ _context
4058
+ );
4059
+ }
4060
+ } catch (e) {
4061
+ result.seedApplied = { success: false, error: e?.message ?? "seed apply failed" };
4062
+ }
4001
4063
  return { handled: true, response: this.success(result) };
4002
4064
  } catch (e) {
4003
4065
  return { handled: true, response: this.error(e.message, e.statusCode || 500) };
@@ -4190,6 +4252,66 @@ var _HttpDispatcher = class _HttpDispatcher {
4190
4252
  * Physical database addressing (database_url, database_driver, etc.)
4191
4253
  * is stored directly on the sys_environment row.
4192
4254
  */
4255
+ /**
4256
+ * Apply just-published `seed` metadata: load each seed's rows into its
4257
+ * target object so publishing a seed draft makes the data live (the runtime
4258
+ * counterpart to staging it). Reads each seed body via the protocol, then
4259
+ * runs the {@link SeedLoaderService} for the active org. Best-effort and
4260
+ * idempotent (upsert) — callers must never let this fail the publish.
4261
+ *
4262
+ * Lives at the runtime layer (not in the objectql publish primitive)
4263
+ * because the seed loader needs the data engine + metadata service, which
4264
+ * objectql cannot depend on without a layering cycle.
4265
+ */
4266
+ async applyPublishedSeeds(names, organizationId, _context) {
4267
+ const protocol = await this.resolveService("protocol");
4268
+ const metadata = await this.getService(import_system2.CoreServiceName.enum.metadata);
4269
+ const ql = await this.resolveService("objectql");
4270
+ if (!protocol || typeof protocol.getMetaItem !== "function" || !ql || !metadata) {
4271
+ return { success: false, error: "seed apply: required services unavailable" };
4272
+ }
4273
+ const datasets = [];
4274
+ const readErrors = [];
4275
+ for (const name of names) {
4276
+ const attempts = organizationId ? [{ type: "seed", name, organizationId }, { type: "seed", name }] : [{ type: "seed", name }];
4277
+ let item;
4278
+ for (const args of attempts) {
4279
+ try {
4280
+ item = await protocol.getMetaItem(args);
4281
+ if (item) break;
4282
+ } catch (e) {
4283
+ readErrors.push(`read ${name}: ${e?.message ?? String(e)}`);
4284
+ }
4285
+ }
4286
+ const seed = item?.object && Array.isArray(item?.records) ? item : item?.item ?? item?.metadata ?? item?.body;
4287
+ if (seed?.object && Array.isArray(seed?.records)) {
4288
+ datasets.push(seed);
4289
+ } else {
4290
+ readErrors.push(`seed "${name}" body unreadable (keys: ${item ? Object.keys(item).join(",") : "none"})`);
4291
+ }
4292
+ }
4293
+ if (datasets.length === 0) {
4294
+ return { success: false, inserted: 0, updated: 0, error: "seed apply: no readable seed bodies", errors: readErrors };
4295
+ }
4296
+ const { SeedLoaderService: SeedLoaderService2 } = await Promise.resolve().then(() => (init_seed_loader(), seed_loader_exports));
4297
+ const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
4298
+ const loader = new SeedLoaderService2(ql, metadata, this.logger ?? console);
4299
+ const request = SeedLoaderRequestSchema.parse({
4300
+ seeds: datasets,
4301
+ config: {
4302
+ defaultMode: "upsert",
4303
+ multiPass: true,
4304
+ ...organizationId ? { organizationId } : {}
4305
+ }
4306
+ });
4307
+ const r = await loader.load(request);
4308
+ return {
4309
+ success: r.success,
4310
+ inserted: r.summary.totalInserted,
4311
+ updated: r.summary.totalUpdated,
4312
+ errors: [...readErrors, ...r.errors ?? []]
4313
+ };
4314
+ }
4193
4315
  /**
4194
4316
  * Resolve the calling user id from the request session, if any.
4195
4317
  * Returns `undefined` for anonymous calls or when auth is not wired up.
@@ -4774,6 +4896,12 @@ var _HttpDispatcher = class _HttpDispatcher {
4774
4896
  if (cleanPath.startsWith("/data")) {
4775
4897
  return this.handleData(cleanPath.substring(5), method, body, query, context);
4776
4898
  }
4899
+ if (cleanPath === "/mcp" || cleanPath.startsWith("/mcp/") || cleanPath.startsWith("/mcp?")) {
4900
+ return this.handleMcp(body, context);
4901
+ }
4902
+ if (cleanPath === "/keys" || cleanPath.startsWith("/keys/") || cleanPath.startsWith("/keys?")) {
4903
+ return this.handleKeys(method, body, context);
4904
+ }
4777
4905
  if (cleanPath.startsWith("/graphql")) {
4778
4906
  if (method === "POST") return this.handleGraphQL(body, context);
4779
4907
  }
@@ -5476,6 +5604,28 @@ function createDispatcherPlugin(config = {}) {
5476
5604
  errorResponse(err, res);
5477
5605
  }
5478
5606
  });
5607
+ const mountMcp = (method) => {
5608
+ const register = method === "GET" ? server.get : method === "DELETE" ? server.delete : server.post;
5609
+ register.call(server, `${prefix}/mcp`, async (req, res) => {
5610
+ try {
5611
+ const result = await dispatcher.dispatch(method, "/mcp", req.body, req.query, { request: req });
5612
+ sendResult(result, res);
5613
+ } catch (err) {
5614
+ errorResponse(err, res);
5615
+ }
5616
+ });
5617
+ };
5618
+ mountMcp("POST");
5619
+ mountMcp("GET");
5620
+ mountMcp("DELETE");
5621
+ server.post(`${prefix}/keys`, async (req, res) => {
5622
+ try {
5623
+ const result = await dispatcher.dispatch("POST", "/keys", req.body, req.query, { request: req });
5624
+ sendResult(result, res);
5625
+ } catch (err) {
5626
+ errorResponse(err, res);
5627
+ }
5628
+ });
5479
5629
  server.get(`${prefix}/packages`, async (req, res) => {
5480
5630
  try {
5481
5631
  const result = await dispatcher.handlePackages("", "GET", {}, req.query, { request: req });
@@ -6121,1624 +6271,63 @@ var MiddlewareManager = class {
6121
6271
  // src/index.ts
6122
6272
  init_load_artifact_bundle();
6123
6273
 
6124
- // src/cloud/kernel-manager.ts
6125
- var KernelManager = class {
6126
- constructor(config) {
6127
- this.cache = /* @__PURE__ */ new Map();
6128
- this.pending = /* @__PURE__ */ new Map();
6129
- this.factory = config.factory;
6130
- this.maxSize = config.maxSize ?? 32;
6131
- this.ttlMs = config.ttlMs ?? 15 * 60 * 1e3;
6132
- this.logger = config.logger ?? console;
6133
- this.freshnessProbe = config.freshnessProbe;
6134
- this.staleCheckIntervalMs = config.staleCheckIntervalMs ?? 1e4;
6135
- }
6136
- /** Returns the currently cached environmentIds (ordered by insertion). */
6137
- keys() {
6138
- return Array.from(this.cache.keys());
6139
- }
6140
- /** Cache size for diagnostics. */
6141
- get size() {
6142
- return this.cache.size;
6143
- }
6144
- /**
6145
- * Resolve or construct the kernel for `environmentId`.
6146
- *
6147
- * - Cache hit (fresh): bumps `lastAccess` and returns immediately.
6148
- * - Cache hit (TTL expired): evicts then falls through to factory.
6149
- * - Cache miss: dedupes concurrent callers through `pending`.
6150
- */
6151
- async getOrCreate(environmentId) {
6152
- const existing = this.cache.get(environmentId);
6153
- if (existing) {
6154
- if (this.ttlMs > 0 && Date.now() - existing.lastAccess > this.ttlMs) {
6155
- await this.evict(environmentId);
6156
- } else {
6157
- if (this.freshnessProbe) {
6158
- const now = Date.now();
6159
- if (now - existing.lastStaleCheckAt >= this.staleCheckIntervalMs) {
6160
- existing.lastStaleCheckAt = now;
6161
- let stale = false;
6162
- try {
6163
- stale = await this.freshnessProbe(environmentId, existing.createdAt);
6164
- } catch (err) {
6165
- this.logger.warn?.("[KernelManager] freshness probe failed", { environmentId, err });
6166
- }
6167
- if (stale) {
6168
- this.logger.info?.("[KernelManager] kernel evicted by freshness probe", { environmentId });
6169
- await this.evict(environmentId);
6170
- } else {
6171
- existing.lastAccess = Date.now();
6172
- return existing.kernel;
6173
- }
6174
- } else {
6175
- existing.lastAccess = Date.now();
6176
- return existing.kernel;
6177
- }
6178
- } else {
6179
- existing.lastAccess = Date.now();
6180
- return existing.kernel;
6181
- }
6182
- }
6183
- }
6184
- const inflight = this.pending.get(environmentId);
6185
- if (inflight) return inflight;
6186
- const promise = (async () => {
6187
- const kernel = await this.factory.create(environmentId);
6188
- const now = Date.now();
6189
- this.cache.set(environmentId, { kernel, createdAt: now, lastAccess: now, lastStaleCheckAt: now });
6190
- await this.enforceMaxSize();
6191
- return kernel;
6192
- })();
6193
- this.pending.set(environmentId, promise);
6194
- try {
6195
- return await promise;
6196
- } finally {
6197
- this.pending.delete(environmentId);
6198
- }
6274
+ // src/cloud/cloud-url.ts
6275
+ var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
6276
+ function resolveCloudUrl(explicit) {
6277
+ const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
6278
+ const lower = raw.toLowerCase();
6279
+ if (lower === "off" || lower === "none" || lower === "local" || lower === "disabled") {
6280
+ return "";
6199
6281
  }
6200
- /**
6201
- * Evict the kernel for `environmentId` and invoke `kernel.shutdown()`.
6202
- * No-op when the entry is absent.
6203
- */
6204
- async evict(environmentId) {
6205
- const entry = this.cache.get(environmentId);
6206
- if (!entry) return;
6207
- this.cache.delete(environmentId);
6208
- try {
6209
- await entry.kernel.shutdown();
6210
- } catch (err) {
6211
- this.logger.error?.("[KernelManager] shutdown failed", { environmentId, err });
6212
- }
6282
+ const picked = raw || DEFAULT_CLOUD_URL;
6283
+ return picked.replace(/\/+$/, "");
6284
+ }
6285
+
6286
+ // src/cloud/marketplace-public-url.ts
6287
+ function resolveMarketplacePublicBaseUrl(explicit) {
6288
+ const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
6289
+ const lower = raw.toLowerCase();
6290
+ if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
6291
+ return "";
6213
6292
  }
6214
- /** Evict all resident kernels. Used on runtime shutdown. */
6215
- async evictAll() {
6216
- const ids = Array.from(this.cache.keys());
6217
- await Promise.all(ids.map((id) => this.evict(id)));
6293
+ return raw.replace(/\/+$/, "");
6294
+ }
6295
+ function publicMarketplaceKeyForApiPath(pathname) {
6296
+ const prefix = "/api/v1/marketplace/packages";
6297
+ if (pathname === prefix) return "packages.json";
6298
+ if (!pathname.startsWith(`${prefix}/`)) return null;
6299
+ const tail = pathname.slice(prefix.length + 1);
6300
+ if (!tail) return null;
6301
+ const parts = tail.split("/");
6302
+ if (parts.length === 1) {
6303
+ const id = decodeURIComponent(parts[0] ?? "");
6304
+ if (!id) return null;
6305
+ return `packages/${encodeURIComponent(id)}.json`;
6218
6306
  }
6219
- async enforceMaxSize() {
6220
- while (this.cache.size > this.maxSize) {
6221
- let oldestKey;
6222
- let oldestAccess = Infinity;
6223
- for (const [key, entry] of this.cache) {
6224
- if (entry.lastAccess < oldestAccess) {
6225
- oldestAccess = entry.lastAccess;
6226
- oldestKey = key;
6227
- }
6228
- }
6229
- if (!oldestKey) return;
6230
- await this.evict(oldestKey);
6231
- }
6307
+ if (parts.length === 4 && parts[1] === "versions" && parts[3] === "manifest") {
6308
+ const id = decodeURIComponent(parts[0] ?? "");
6309
+ const versionId = decodeURIComponent(parts[2] ?? "");
6310
+ if (!id || !versionId) return null;
6311
+ return `packages/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionId)}/manifest.json`;
6232
6312
  }
6233
- };
6313
+ return null;
6314
+ }
6234
6315
 
6235
- // src/cloud/artifact-api-client.ts
6236
- var ArtifactApiClient = class {
6237
- constructor(config) {
6238
- this.hostnameCache = /* @__PURE__ */ new Map();
6239
- this.artifactCache = /* @__PURE__ */ new Map();
6240
- this.pendingHostname = /* @__PURE__ */ new Map();
6241
- this.pendingArtifact = /* @__PURE__ */ new Map();
6242
- if (!config.controlPlaneUrl) {
6243
- throw new Error("[ArtifactApiClient] controlPlaneUrl is required");
6244
- }
6245
- this.base = config.controlPlaneUrl.replace(/\/+$/, "");
6246
- this.apiKey = config.apiKey;
6247
- this.cacheTtlMs = config.cacheTtlMs ?? 5 * 60 * 1e3;
6248
- this.requestTimeoutMs = config.requestTimeoutMs ?? 1e4;
6249
- this.fetchImpl = config.fetch ?? globalThis.fetch;
6250
- this.logger = config.logger ?? console;
6251
- if (typeof this.fetchImpl !== "function") {
6252
- throw new Error("[ArtifactApiClient] global fetch is not available \u2014 provide config.fetch");
6253
- }
6254
- }
6255
- /**
6256
- * Resolve a hostname to its project. Returns `null` on 404 or
6257
- * malformed responses. Errors (network / 5xx) are thrown so
6258
- * upstream callers can retry.
6259
- */
6260
- async resolveHostname(host) {
6261
- const cached = this.hostnameCache.get(host);
6262
- if (cached && cached.expiresAt > Date.now()) return cached.value;
6263
- const inflight = this.pendingHostname.get(host);
6264
- if (inflight) return inflight;
6265
- const promise = (async () => {
6266
- try {
6267
- const url = `${this.base}/api/v1/cloud/resolve-hostname?host=${encodeURIComponent(host)}`;
6268
- const res = await this.request(url);
6269
- if (res === null) return null;
6270
- const body = res.success === false ? null : res.data ?? res;
6271
- if (!body || typeof body.environmentId !== "string" || !body.environmentId) return null;
6272
- const value = {
6273
- environmentId: body.environmentId,
6274
- organizationId: body.organizationId,
6275
- runtime: body.runtime
6276
- };
6277
- this.hostnameCache.set(host, { value, expiresAt: Date.now() + this.cacheTtlMs });
6278
- return value;
6279
- } finally {
6280
- this.pendingHostname.delete(host);
6281
- }
6282
- })();
6283
- this.pendingHostname.set(host, promise);
6284
- return promise;
6285
- }
6286
- /**
6287
- * Fetch the compiled artifact for a project.
6288
- *
6289
- * When `opts.commit` is set, requests that specific revision via the
6290
- * existing `?commit=` query param. Different commits are cached
6291
- * independently (the cache key includes the commit id) so the preview
6292
- * runtime can hold multiple versions in memory simultaneously.
6293
- */
6294
- async fetchArtifact(environmentId, opts) {
6295
- const commit = opts?.commit?.trim() || "";
6296
- const cacheKey = commit ? `${environmentId}@${commit}` : environmentId;
6297
- const cached = this.artifactCache.get(cacheKey);
6298
- if (cached && cached.expiresAt > Date.now()) return cached.value;
6299
- const inflight = this.pendingArtifact.get(cacheKey);
6300
- if (inflight) return inflight;
6301
- const promise = (async () => {
6302
- try {
6303
- const qs = commit ? `?commit=${encodeURIComponent(commit)}` : "";
6304
- const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/artifact${qs}`;
6305
- const res = await this.request(url);
6306
- if (res === null) return null;
6307
- const body = res.success === false ? null : res.data ?? res;
6308
- if (!body || typeof body !== "object") return null;
6309
- if (!body.metadata) {
6310
- this.logger.warn?.("[ArtifactApiClient] artifact response missing `metadata`", { environmentId, commit });
6311
- return null;
6312
- }
6313
- const value = body;
6314
- this.artifactCache.set(cacheKey, { value, expiresAt: Date.now() + this.cacheTtlMs });
6315
- return value;
6316
- } finally {
6317
- this.pendingArtifact.delete(cacheKey);
6318
- }
6319
- })();
6320
- this.pendingArtifact.set(cacheKey, promise);
6321
- return promise;
6322
- }
6323
- /**
6324
- * Resolve an 8-hex project short id (first 8 hex chars of the UUID,
6325
- * dashes stripped) to the full environmentId. Used by the preview
6326
- * runtime, which encodes project ids in subdomains.
6327
- *
6328
- * Returns `null` on 404 or ambiguity (the control plane returns 409
6329
- * if the prefix matches more than one project).
6330
- */
6331
- async lookupProjectByShortId(shortId) {
6332
- const short = String(shortId ?? "").trim().toLowerCase();
6333
- if (!/^[0-9a-f]{8,}$/.test(short)) return null;
6334
- const url = `${this.base}/api/v1/cloud/environments-by-short-id/${encodeURIComponent(short)}`;
6335
- const res = await this.request(url);
6336
- if (res === null) return null;
6337
- const body = res.success === false ? null : res.data ?? res;
6338
- if (!body || typeof body.environmentId !== "string" || !body.environmentId) return null;
6339
- return { environmentId: body.environmentId, organizationId: body.organizationId };
6340
- }
6341
- /**
6342
- * Fetch the head commit of a branch. Returns the commit id (and the
6343
- * matching revision row's `published_at` for cache-validity checks).
6344
- * Reuses the existing `GET /cloud/environments/:id/branches` endpoint.
6345
- */
6346
- async fetchBranchHead(environmentId, branchName) {
6347
- const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/branches`;
6348
- const res = await this.request(url);
6349
- if (res === null) return null;
6350
- const body = res.success === false ? null : res.data ?? res;
6351
- const branches = Array.isArray(body?.branches) ? body.branches : [];
6352
- const target = String(branchName ?? "").trim().toLowerCase();
6353
- const found = branches.find((b) => String(b?.branch ?? "").toLowerCase() === target);
6354
- if (!found?.headCommitId) return null;
6355
- return { commitId: String(found.headCommitId), publishedAt: found.headPublishedAt ?? null };
6356
- }
6357
- /**
6358
- * Cheap freshness probe — returns the env's `last_published_at`
6359
- * (and best-effort current commit) without rebuilding the artifact.
6360
- * Used by `KernelManager` on cache hits to detect when a per-env
6361
- * kernel has been invalidated by an upstream change (marketplace
6362
- * install/uninstall, artifact publish) so it can be rebuilt
6363
- * without waiting for the 15-minute LRU TTL to expire.
6364
- *
6365
- * Returns `null` on definitive 404 / unknown env. Errors propagate
6366
- * (caller decides whether to treat unreachable cloud as fresh or
6367
- * stale — typically fresh, so a brief outage doesn't churn every
6368
- * cached kernel).
6369
- */
6370
- async getFreshness(environmentId) {
6371
- const url = `${this.base}/api/v1/cloud/environments/${encodeURIComponent(environmentId)}/freshness`;
6372
- const res = await this.request(url);
6373
- if (res === null) return null;
6374
- const body = res.success === false ? null : res.data ?? res;
6375
- if (!body || typeof body !== "object") return null;
6376
- const envId = typeof body.environmentId === "string" ? body.environmentId : environmentId;
6377
- const lastPublishedAt = typeof body.lastPublishedAt === "string" ? body.lastPublishedAt : null;
6378
- const commitId = typeof body.commitId === "string" ? body.commitId : null;
6379
- return { environmentId: envId, lastPublishedAt, commitId };
6380
- }
6381
- /** Drop cached entries for a project (and any matching hostname). */
6382
- invalidate(environmentId) {
6383
- this.artifactCache.delete(environmentId);
6384
- const prefix = `${environmentId}@`;
6385
- for (const key of Array.from(this.artifactCache.keys())) {
6386
- if (key.startsWith(prefix)) this.artifactCache.delete(key);
6387
- }
6388
- for (const [host, entry] of this.hostnameCache) {
6389
- if (entry.value.environmentId === environmentId) this.hostnameCache.delete(host);
6390
- }
6391
- }
6392
- /** Drop everything. Used on shutdown / hot-reload. */
6393
- clear() {
6394
- this.hostnameCache.clear();
6395
- this.artifactCache.clear();
6396
- }
6397
- async request(url) {
6398
- const controller = typeof AbortController !== "undefined" ? new AbortController() : null;
6399
- const timer = controller ? setTimeout(() => controller.abort(), this.requestTimeoutMs) : null;
6400
- try {
6401
- const res = await this.fetchImpl(url, {
6402
- method: "GET",
6403
- headers: this.buildHeaders(),
6404
- signal: controller?.signal
6405
- });
6406
- if (res.status === 404) return null;
6407
- if (!res.ok) {
6408
- throw new Error(`[ArtifactApiClient] ${url} \u2192 HTTP ${res.status}`);
6409
- }
6410
- return await res.json();
6411
- } finally {
6412
- if (timer) clearTimeout(timer);
6413
- }
6414
- }
6415
- buildHeaders() {
6416
- const headers = {
6417
- "accept": "application/json",
6418
- "user-agent": "objectos-runtime"
6419
- };
6420
- if (this.apiKey) headers["authorization"] = `Bearer ${this.apiKey}`;
6421
- return headers;
6422
- }
6423
- };
6424
-
6425
- // src/cloud/artifact-environment-registry.ts
6426
- var import_node_path5 = require("path");
6427
- var ArtifactEnvironmentRegistry = class {
6428
- constructor(config) {
6429
- this.hostnameCache = /* @__PURE__ */ new Map();
6430
- this.idCache = /* @__PURE__ */ new Map();
6431
- this.pending = /* @__PURE__ */ new Map();
6432
- this.client = config.client;
6433
- this.cacheTTL = config.cacheTtlMs ?? 5 * 60 * 1e3;
6434
- this.logger = config.logger ?? console;
6435
- }
6436
- async resolveByHostname(host) {
6437
- const cached = this.hostnameCache.get(host);
6438
- if (cached && cached.expiresAt > Date.now()) {
6439
- return { environmentId: cached.environmentId, driver: cached.driver };
6440
- }
6441
- const key = `host:${host}`;
6442
- const inflight = this.pending.get(key);
6443
- if (inflight) {
6444
- const result = await inflight;
6445
- return result ? { environmentId: result.environmentId, driver: result.driver } : null;
6446
- }
6447
- const promise = (async () => {
6448
- try {
6449
- const resolved = await this.client.resolveHostname(host);
6450
- if (!resolved) return null;
6451
- const entry2 = await this.buildCacheEntry(resolved.environmentId, resolved.runtime, resolved.organizationId, host);
6452
- if (!entry2) return null;
6453
- this.hostnameCache.set(host, entry2);
6454
- this.idCache.set(entry2.environmentId, entry2);
6455
- return entry2;
6456
- } catch (err) {
6457
- this.logger.error?.("[ArtifactEnvironmentRegistry] resolveByHostname failed", {
6458
- host,
6459
- error: err?.message ?? err
6460
- });
6461
- return null;
6462
- } finally {
6463
- this.pending.delete(key);
6464
- }
6465
- })();
6466
- this.pending.set(key, promise);
6467
- const entry = await promise;
6468
- return entry ? { environmentId: entry.environmentId, driver: entry.driver } : null;
6469
- }
6470
- async resolveById(environmentId) {
6471
- const cached = this.idCache.get(environmentId);
6472
- if (cached && cached.expiresAt > Date.now()) return cached.driver;
6473
- const key = `id:${environmentId}`;
6474
- const inflight = this.pending.get(key);
6475
- if (inflight) {
6476
- const result = await inflight;
6477
- return result?.driver ?? null;
6478
- }
6479
- const promise = (async () => {
6480
- try {
6481
- const entry2 = await this.buildCacheEntry(environmentId, void 0, void 0, void 0);
6482
- if (!entry2) return null;
6483
- this.idCache.set(environmentId, entry2);
6484
- if (entry2.project?.hostname) this.hostnameCache.set(entry2.project.hostname, entry2);
6485
- return entry2;
6486
- } catch (err) {
6487
- this.logger.error?.("[ArtifactEnvironmentRegistry] resolveById failed", {
6488
- environmentId,
6489
- error: err?.message ?? err
6490
- });
6491
- return null;
6492
- } finally {
6493
- this.pending.delete(key);
6494
- }
6495
- })();
6496
- this.pending.set(key, promise);
6497
- const entry = await promise;
6498
- return entry?.driver ?? null;
6499
- }
6500
- peekById(environmentId) {
6501
- const cached = this.idCache.get(environmentId);
6502
- if (cached && cached.expiresAt > Date.now()) {
6503
- return { environmentId: cached.environmentId, driver: cached.driver, project: cached.project };
6504
- }
6505
- return null;
6506
- }
6507
- invalidate(environmentId) {
6508
- this.idCache.delete(environmentId);
6509
- for (const [host, entry] of this.hostnameCache) {
6510
- if (entry.environmentId === environmentId) this.hostnameCache.delete(host);
6511
- }
6512
- this.client.invalidate(environmentId);
6513
- }
6514
- async buildCacheEntry(environmentId, runtimeFromHostname, orgIdFromHostname, hostname) {
6515
- let runtime = runtimeFromHostname;
6516
- let organizationId = orgIdFromHostname;
6517
- let host = hostname;
6518
- let artifactProjectId = environmentId;
6519
- if (!runtime || !organizationId) {
6520
- const artifact = await this.client.fetchArtifact(environmentId);
6521
- if (!artifact) {
6522
- this.logger.warn?.("[ArtifactEnvironmentRegistry] artifact not found", { environmentId });
6523
- return null;
6524
- }
6525
- artifactProjectId = artifact.environmentId ?? environmentId;
6526
- if (!runtime) runtime = artifact.runtime ?? extractRuntimeFromMetadata(artifact.metadata);
6527
- if (!organizationId) organizationId = artifact.runtime?.organizationId;
6528
- if (!host) host = artifact.runtime?.hostname;
6529
- }
6530
- if (!runtime || !runtime.databaseUrl || !runtime.databaseDriver) {
6531
- this.logger.warn?.("[ArtifactEnvironmentRegistry] no runtime config for project", { environmentId });
6532
- return null;
6533
- }
6534
- const driver = await createDriver(runtime.databaseDriver, runtime.databaseUrl, runtime.databaseAuthToken ?? "");
6535
- const projectRow = {
6536
- id: artifactProjectId,
6537
- organization_id: organizationId,
6538
- hostname: host,
6539
- database_url: runtime.databaseUrl,
6540
- database_driver: runtime.databaseDriver,
6541
- metadata: runtime.metadata
6542
- };
6543
- return {
6544
- environmentId: artifactProjectId,
6545
- driver,
6546
- project: projectRow,
6547
- expiresAt: Date.now() + this.cacheTTL
6548
- };
6549
- }
6550
- };
6551
- function extractRuntimeFromMetadata(metadata) {
6552
- const datasources = metadata?.datasources;
6553
- if (!Array.isArray(datasources) || datasources.length === 0) return void 0;
6554
- const mapping = metadata?.datasourceMapping;
6555
- let preferredName;
6556
- if (mapping) {
6557
- const def = mapping.find((m) => m?.default === true);
6558
- if (def?.datasource) preferredName = def.datasource;
6559
- }
6560
- const ds = preferredName ? datasources.find((d) => d?.name === preferredName) : datasources[0];
6561
- if (!ds || typeof ds !== "object") return void 0;
6562
- const config = ds.config ?? {};
6563
- const url = config.url ?? config.connectionString ?? config.connection ?? config.filename;
6564
- const driver = ds.driver;
6565
- if (typeof driver !== "string" || typeof url !== "string") return void 0;
6566
- return {
6567
- databaseDriver: driver,
6568
- databaseUrl: url,
6569
- databaseAuthToken: typeof config.authToken === "string" ? config.authToken : void 0
6570
- };
6571
- }
6572
- async function createDriver(driverType, databaseUrl, authToken) {
6573
- switch (driverType) {
6574
- case "libsql":
6575
- case "turso": {
6576
- let TursoDriver;
6577
- try {
6578
- ({ TursoDriver } = await import("@objectstack/driver-turso"));
6579
- } catch (primaryErr) {
6580
- try {
6581
- const { createRequire } = await import("module");
6582
- const path = await import("path");
6583
- const url = await import("url");
6584
- const hostRequire = createRequire(path.join(process.cwd(), "noop.js"));
6585
- const resolved = hostRequire.resolve("@objectstack/driver-turso");
6586
- ({ TursoDriver } = await import(url.pathToFileURL(resolved).href));
6587
- } catch (fallbackErr) {
6588
- throw new Error(
6589
- `[ArtifactEnvironmentRegistry] libsql/turso driver requested but @objectstack/driver-turso is not resolvable. Install it from the cloud monorepo (cloud/packages/driver-turso) or via npm. (primary: ${primaryErr?.message ?? primaryErr}; fallback: ${fallbackErr?.message ?? fallbackErr})`
6590
- );
6591
- }
6592
- }
6593
- return new TursoDriver({ url: databaseUrl, authToken });
6594
- }
6595
- case "memory": {
6596
- const { InMemoryDriver } = await import("@objectstack/driver-memory");
6597
- const dbName = databaseUrl.replace(/^memory:\/\//, "").trim();
6598
- const filePath = dbName ? (0, import_node_path5.resolve)(process.cwd(), ".objectstack/data/projects", `${dbName}.json`) : void 0;
6599
- return new InMemoryDriver({
6600
- persistence: filePath ? { type: "file", path: filePath } : "file"
6601
- });
6602
- }
6603
- case "sqlite":
6604
- case "sql": {
6605
- const filePath = databaseUrl.replace(/^file:/, "").replace(/^sql:\/\//, "");
6606
- const { SqlDriver } = await import("@objectstack/driver-sql");
6607
- return new SqlDriver({
6608
- client: "better-sqlite3",
6609
- connection: { filename: filePath },
6610
- useNullAsDefault: true
6611
- });
6612
- }
6613
- case "postgres":
6614
- case "postgresql":
6615
- case "pg": {
6616
- const { SqlDriver } = await import("@objectstack/driver-sql");
6617
- return new SqlDriver({
6618
- client: "pg",
6619
- connection: databaseUrl,
6620
- pool: { min: 0, max: 5 }
6621
- });
6622
- }
6623
- case "mongodb":
6624
- case "mongo": {
6625
- const { MongoDBDriver } = await import("@objectstack/driver-mongodb");
6626
- return new MongoDBDriver({ url: databaseUrl });
6627
- }
6628
- default:
6629
- throw new Error(`[ArtifactEnvironmentRegistry] Unsupported driver type: ${driverType}`);
6630
- }
6631
- }
6632
-
6633
- // src/cloud/artifact-kernel-factory.ts
6634
- var import_node_crypto2 = require("crypto");
6635
- var import_core3 = require("@objectstack/core");
6636
- var import_types3 = require("@objectstack/types");
6637
- init_driver_plugin();
6638
- init_app_plugin();
6639
-
6640
- // src/cloud/capability-loader.ts
6641
- var CAPABILITY_PROVIDERS = {
6642
- automation: {
6643
- // Self-contained: AutomationServicePlugin seeds all built-in node
6644
- // executors itself (ADR-0018), so no companion node-pack plugins.
6645
- pkg: "@objectstack/service-automation",
6646
- export: "AutomationServicePlugin"
6647
- },
6648
- ai: {
6649
- pkg: "@objectstack/service-ai",
6650
- export: "AIServicePlugin"
6651
- },
6652
- // AI Studio — AI-driven metadata authoring ("online development"). This is
6653
- // a commercial capability that ships in the private @objectstack/service-ai-studio
6654
- // package (not part of the open-source framework). The dynamic import below
6655
- // silently skips when the package isn't installed, so the open-source build
6656
- // is unaffected; cloud and enterprise installs that ship the package light it
6657
- // up. Pair with `ai` in `requires` (it attaches via the `ai:ready` hook).
6658
- aiStudio: {
6659
- pkg: "@objectstack/service-ai-studio",
6660
- export: "AIStudioPlugin"
6661
- },
6662
- analytics: {
6663
- pkg: "@objectstack/service-analytics",
6664
- export: "AnalyticsServicePlugin",
6665
- configKey: "analyticsCubes"
6666
- },
6667
- audit: {
6668
- pkg: "@objectstack/plugin-audit",
6669
- export: "AuditPlugin"
6670
- },
6671
- cache: {
6672
- pkg: "@objectstack/service-cache",
6673
- export: "CacheServicePlugin"
6674
- },
6675
- storage: {
6676
- pkg: "@objectstack/service-storage",
6677
- export: "StorageServicePlugin"
6678
- },
6679
- queue: {
6680
- pkg: "@objectstack/service-queue",
6681
- export: "QueueServicePlugin"
6682
- },
6683
- job: {
6684
- pkg: "@objectstack/service-job",
6685
- export: "JobServicePlugin"
6686
- },
6687
- messaging: {
6688
- // Backs the `notify` flow node (ADR-0012): delivers to a user's
6689
- // channels (inbox by default → `sys_inbox_message` rows).
6690
- pkg: "@objectstack/service-messaging",
6691
- export: "MessagingServicePlugin"
6692
- },
6693
- triggers: {
6694
- // Concrete flow triggers — record-change (ObjectQL hooks) + schedule
6695
- // (cron/interval via the job service; pair `triggers` with `job`).
6696
- pkg: "@objectstack/plugin-trigger-record-change",
6697
- export: "RecordChangeTriggerPlugin",
6698
- extras: [{ pkg: "@objectstack/plugin-trigger-schedule", export: "ScheduleTriggerPlugin" }]
6699
- },
6700
- realtime: {
6701
- pkg: "@objectstack/service-realtime",
6702
- export: "RealtimeServicePlugin"
6703
- },
6704
- feed: {
6705
- pkg: "@objectstack/service-feed",
6706
- export: "FeedServicePlugin"
6707
- },
6708
- settings: {
6709
- pkg: "@objectstack/service-settings",
6710
- export: "SettingsServicePlugin"
6711
- }
6712
- };
6713
- async function loadCapabilities(opts) {
6714
- const { kernel, requires, bundle, environmentId } = opts;
6715
- const logger = opts.logger ?? console;
6716
- const installed = [];
6717
- const resolved = [...new Set(requires)];
6718
- if (resolved.includes("audit") && !resolved.includes("messaging")) {
6719
- resolved.push("messaging");
6720
- }
6721
- for (const cap of resolved) {
6722
- const spec = CAPABILITY_PROVIDERS[cap];
6723
- if (!spec) {
6724
- continue;
6725
- }
6726
- try {
6727
- const mod = await import(
6728
- /* webpackIgnore: true */
6729
- spec.pkg
6730
- );
6731
- const Ctor = mod[spec.export];
6732
- if (!Ctor) {
6733
- logger.warn?.(
6734
- `[CapabilityLoader] '${cap}': package '${spec.pkg}' did not export '${spec.export}'`,
6735
- { environmentId }
6736
- );
6737
- continue;
6738
- }
6739
- let arg;
6740
- if (spec.configKey) {
6741
- const v = bundle[spec.configKey];
6742
- if (spec.configKey === "analyticsCubes") {
6743
- arg = { cubes: Array.isArray(v) ? v : [] };
6744
- } else if (v !== void 0) {
6745
- arg = v;
6746
- }
6747
- }
6748
- await kernel.use(arg !== void 0 ? new Ctor(arg) : new Ctor());
6749
- installed.push(spec.export);
6750
- if (spec.extras) {
6751
- for (const ex of spec.extras) {
6752
- try {
6753
- const exMod = await import(
6754
- /* webpackIgnore: true */
6755
- ex.pkg
6756
- );
6757
- const ExCtor = exMod[ex.export];
6758
- if (ExCtor) {
6759
- await kernel.use(new ExCtor());
6760
- installed.push(ex.export);
6761
- }
6762
- } catch {
6763
- }
6764
- }
6765
- }
6766
- logger.info?.(
6767
- `[CapabilityLoader] '${cap}' installed (${spec.export}${spec.extras ? " + " + spec.extras.length + " extras" : ""})`,
6768
- { environmentId }
6769
- );
6770
- } catch (err) {
6771
- const msg = err?.message ?? String(err);
6772
- if (msg.includes("Cannot find module") || msg.includes("ERR_MODULE_NOT_FOUND")) {
6773
- logger.warn?.(
6774
- `[CapabilityLoader] '${cap}' requested but '${spec.pkg}' not installed in host \u2014 skipped`,
6775
- { environmentId }
6776
- );
6777
- } else {
6778
- logger.error?.(
6779
- `[CapabilityLoader] '${cap}' load failed: ${msg}`,
6780
- { environmentId }
6781
- );
6782
- }
6783
- }
6784
- }
6785
- return installed;
6786
- }
6787
-
6788
- // src/cloud/platform-sso.ts
6789
- var import_node_crypto = require("crypto");
6790
- var PLATFORM_SSO_PROVIDER_ID = "objectstack-cloud";
6791
- function derivePlatformSsoClientId(environmentId) {
6792
- return `project_${environmentId}`;
6793
- }
6794
- function derivePlatformSsoClientSecret(baseSecret, environmentId) {
6795
- return (0, import_node_crypto.createHmac)("sha256", baseSecret).update(`oauth-client:${environmentId}`).digest("hex");
6796
- }
6797
- function hashPlatformSsoClientSecret(plaintext) {
6798
- return (0, import_node_crypto.createHash)("sha256").update(plaintext).digest("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_");
6799
- }
6800
- function buildPlatformSsoRedirectUri(hostname, basePath = "/api/v1/auth") {
6801
- let host;
6802
- if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
6803
- host = hostname;
6804
- } else if (/(\.|^)localhost(:\d+)?$/i.test(hostname)) {
6805
- const port = (process.env.OS_RUNTIME_PORT ?? "").trim();
6806
- const hostWithPort = /:\d+$/.test(hostname) || !port ? hostname : `${hostname}:${port}`;
6807
- host = `http://${hostWithPort}`;
6808
- } else {
6809
- host = `https://${hostname}`;
6810
- }
6811
- const trimmed = host.replace(/\/+$/, "");
6812
- const path = basePath.replace(/\/+$/, "");
6813
- return `${trimmed}${path}/oauth2/callback/${PLATFORM_SSO_PROVIDER_ID}`;
6814
- }
6815
- async function seedPlatformSsoClient(opts) {
6816
- const { ql, environmentId, hostname, baseSecret, logger, throwOnError } = opts;
6817
- if (!baseSecret) {
6818
- logger?.warn?.("[platform-sso] OS_AUTH_SECRET not set \u2014 skipping client seed", { environmentId });
6819
- return;
6820
- }
6821
- const clientId = derivePlatformSsoClientId(environmentId);
6822
- const clientSecretPlaintext = derivePlatformSsoClientSecret(baseSecret, environmentId);
6823
- const clientSecretStored = hashPlatformSsoClientSecret(clientSecretPlaintext);
6824
- const desiredRedirect = hostname ? buildPlatformSsoRedirectUri(hostname) : null;
6825
- let existing = null;
6826
- try {
6827
- const rows = await ql.find("sys_oauth_application", {
6828
- where: { client_id: clientId },
6829
- limit: 1
6830
- }, { context: { isSystem: true } });
6831
- const list = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
6832
- existing = list[0] ?? null;
6833
- } catch (err) {
6834
- logger?.warn?.("[platform-sso] sys_oauth_application read failed \u2014 skipping seed", {
6835
- environmentId,
6836
- error: err?.message
6837
- });
6838
- return;
6839
- }
6840
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
6841
- if (!existing) {
6842
- const redirects = desiredRedirect ? [desiredRedirect] : [];
6843
- try {
6844
- await ql.insert("sys_oauth_application", {
6845
- id: `oauthc_${environmentId}`,
6846
- name: `Project ${environmentId}`,
6847
- client_id: clientId,
6848
- client_secret: clientSecretStored,
6849
- type: "web",
6850
- redirect_uris: JSON.stringify(redirects),
6851
- grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
6852
- response_types: JSON.stringify(["code"]),
6853
- scopes: JSON.stringify(["openid", "email", "profile"]),
6854
- token_endpoint_auth_method: "client_secret_basic",
6855
- require_pkce: false,
6856
- skip_consent: true,
6857
- disabled: false,
6858
- subject_type: "public",
6859
- created_at: nowIso,
6860
- updated_at: nowIso
6861
- }, { context: { isSystem: true } });
6862
- logger?.info?.("[platform-sso] sys_oauth_application row created", { environmentId, clientId });
6863
- } catch (err) {
6864
- logger?.warn?.("[platform-sso] sys_oauth_application create failed", {
6865
- environmentId,
6866
- error: err?.message
6867
- });
6868
- if (throwOnError) throw err;
6869
- }
6870
- return;
6871
- }
6872
- let currentRedirects = [];
6873
- try {
6874
- const raw = existing.redirect_uris;
6875
- const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
6876
- if (Array.isArray(parsed)) currentRedirects = parsed.filter((s) => typeof s === "string");
6877
- } catch {
6878
- }
6879
- const mergedRedirects = desiredRedirect && !currentRedirects.includes(desiredRedirect) ? [...currentRedirects, desiredRedirect] : currentRedirects;
6880
- const repairPatch = {
6881
- name: existing.name || `Project ${environmentId}`,
6882
- client_secret: clientSecretStored,
6883
- type: existing.type || "web",
6884
- redirect_uris: JSON.stringify(mergedRedirects),
6885
- grant_types: JSON.stringify(["authorization_code", "refresh_token"]),
6886
- response_types: JSON.stringify(["code"]),
6887
- scopes: JSON.stringify(["openid", "email", "profile"]),
6888
- token_endpoint_auth_method: "client_secret_basic",
6889
- require_pkce: false,
6890
- skip_consent: true,
6891
- disabled: false,
6892
- subject_type: "public",
6893
- updated_at: nowIso
6894
- };
6895
- try {
6896
- await ql.update(
6897
- "sys_oauth_application",
6898
- repairPatch,
6899
- { where: { id: existing.id } },
6900
- { context: { isSystem: true } }
6901
- );
6902
- logger?.info?.("[platform-sso] sys_oauth_application repaired", {
6903
- environmentId,
6904
- clientId,
6905
- redirect_uris: mergedRedirects
6906
- });
6907
- } catch (err) {
6908
- logger?.warn?.("[platform-sso] sys_oauth_application repair failed", {
6909
- environmentId,
6910
- error: err?.message
6911
- });
6912
- if (throwOnError) throw err;
6913
- }
6914
- }
6915
- async function backfillPlatformSsoClients(opts) {
6916
- const { ql, baseSecret, logger, limit = 1e3 } = opts;
6917
- if (!baseSecret) {
6918
- logger?.warn?.("[platform-sso] backfill skipped \u2014 OS_AUTH_SECRET not set");
6919
- return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [] };
6920
- }
6921
- let projects = [];
6922
- try {
6923
- const rows = await ql.find("sys_environment", {
6924
- limit,
6925
- fields: ["id", "hostname", "status"]
6926
- }, { context: { isSystem: true } });
6927
- projects = Array.isArray(rows) ? rows : Array.isArray(rows?.records) ? rows.records : [];
6928
- } catch (err) {
6929
- logger?.warn?.("[platform-sso] backfill: sys_environment read failed", {
6930
- error: err?.message
6931
- });
6932
- return { scanned: 0, seeded: 0, alreadyExisted: 0, failures: [{ environmentId: "<scan>", error: err?.message ?? String(err) }] };
6933
- }
6934
- let seeded = 0;
6935
- let alreadyExisted = 0;
6936
- const failures = [];
6937
- for (const p of projects) {
6938
- if (!p?.id) continue;
6939
- const before = await (async () => {
6940
- try {
6941
- const r = await ql.find("sys_oauth_application", {
6942
- where: { client_id: derivePlatformSsoClientId(p.id) },
6943
- limit: 1
6944
- }, { context: { isSystem: true } });
6945
- const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
6946
- return list[0] ?? null;
6947
- } catch {
6948
- return null;
6949
- }
6950
- })();
6951
- try {
6952
- await seedPlatformSsoClient({ ql, environmentId: p.id, hostname: p.hostname, baseSecret, logger, throwOnError: true });
6953
- if (before) alreadyExisted++;
6954
- else {
6955
- const after = await (async () => {
6956
- try {
6957
- const r = await ql.find("sys_oauth_application", {
6958
- where: { client_id: derivePlatformSsoClientId(p.id) },
6959
- limit: 1
6960
- }, { context: { isSystem: true } });
6961
- const list = Array.isArray(r) ? r : Array.isArray(r?.records) ? r.records : [];
6962
- return list[0] ?? null;
6963
- } catch (err) {
6964
- return { _readErr: err?.message };
6965
- }
6966
- })();
6967
- if (after && !after._readErr) seeded++;
6968
- else failures.push({ environmentId: p.id, error: `post-insert read returned ${after ? JSON.stringify(after) : "null"}` });
6969
- }
6970
- } catch (err) {
6971
- failures.push({ environmentId: p.id, error: err?.message ?? String(err) });
6972
- }
6973
- }
6974
- logger?.info?.("[platform-sso] backfill complete", { scanned: projects.length, seeded, alreadyExisted, failures: failures.length });
6975
- return { scanned: projects.length, seeded, alreadyExisted, failures };
6976
- }
6977
-
6978
- // src/cloud/artifact-kernel-factory.ts
6979
- function deriveProjectAuthSecret(baseSecret, environmentId) {
6980
- return (0, import_node_crypto2.createHmac)("sha256", baseSecret).update(`project:${environmentId}`).digest("hex");
6981
- }
6982
- var ArtifactKernelFactory = class {
6983
- constructor(config) {
6984
- this.client = config.client;
6985
- this.envRegistry = config.envRegistry;
6986
- this.logger = config.logger ?? console;
6987
- this.kernelConfig = config.kernelConfig;
6988
- this.defaultRequires = config.defaultRequires ?? [];
6989
- this.authBaseSecret = (config.authBaseSecret ?? (0, import_types3.readEnvWithDeprecation)("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
6990
- }
6991
- async create(environmentId) {
6992
- let cached = this.envRegistry.peekById(environmentId);
6993
- if (!cached) {
6994
- const driver2 = await this.envRegistry.resolveById(environmentId);
6995
- if (!driver2) {
6996
- throw new Error(`[ArtifactKernelFactory] Could not resolve driver for project '${environmentId}'`);
6997
- }
6998
- cached = this.envRegistry.peekById(environmentId);
6999
- if (!cached) {
7000
- throw new Error(`[ArtifactKernelFactory] envRegistry returned a driver but no cached entry for '${environmentId}'`);
7001
- }
7002
- }
7003
- const driver = cached.driver;
7004
- const project = cached.project;
7005
- const artifact = await this.client.fetchArtifact(environmentId);
7006
- if (!artifact) {
7007
- throw new Error(`[ArtifactKernelFactory] Artifact not available for project '${environmentId}'`);
7008
- }
7009
- const { ObjectQLPlugin } = await import("@objectstack/objectql");
7010
- const { MetadataPlugin } = await import("@objectstack/metadata");
7011
- const kernel = new import_core3.ObjectKernel(this.kernelConfig);
7012
- await kernel.use(new DriverPlugin(driver, { datasourceName: "cloud" }));
7013
- await kernel.use(new ObjectQLPlugin({ environmentId, skipSchemaSync: false }));
7014
- await kernel.use(new MetadataPlugin({
7015
- watch: false,
7016
- environmentId,
7017
- organizationId: project.organization_id,
7018
- // ADR-0005: customization overlays (user-created views, dashboards,
7019
- // edited objects, ...) are persisted by
7020
- // ObjectStackProtocolImplementation.saveMetaItem on whichever
7021
- // engine the protocol is attached to. For per-project kernels that
7022
- // means the project's own DB, so the sys_metadata + history tables
7023
- // MUST be provisioned here. The previous `false` setting caused
7024
- // "no such table: sys_metadata" errors on any PUT /api/v1/meta/*
7025
- // call (e.g. Studio "Create View") against a project deployment.
7026
- registerSystemObjects: true
7027
- }));
7028
- if (this.authBaseSecret) {
7029
- try {
7030
- const { AuthPlugin } = await import("@objectstack/plugin-auth");
7031
- const projectSecret = deriveProjectAuthSecret(this.authBaseSecret, environmentId);
7032
- const baseUrl = project.hostname ? project.hostname.startsWith("http") ? project.hostname : /(\.|^)localhost(:\d+)?$/i.test(project.hostname) ? (() => {
7033
- const runtimePort = (process.env.OS_RUNTIME_PORT ?? "").trim();
7034
- const hasPort = /:\d+$/.test(project.hostname);
7035
- const hostWithPort = hasPort || !runtimePort ? project.hostname : `${project.hostname}:${runtimePort}`;
7036
- return `http://${hostWithPort}`;
7037
- })() : `https://${project.hostname}` : void 0;
7038
- const trustedOriginsList = [];
7039
- if (baseUrl) trustedOriginsList.push(baseUrl);
7040
- const platformOrigins = (process.env.OS_TRUSTED_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
7041
- for (const o of platformOrigins) {
7042
- if (!trustedOriginsList.includes(o)) trustedOriginsList.push(o);
7043
- }
7044
- const rootDomain = (process.env.OS_ROOT_DOMAIN ?? "").trim().replace(/^https?:\/\//, "");
7045
- if (rootDomain) {
7046
- const wildcard = `https://*.${rootDomain}`;
7047
- if (!trustedOriginsList.includes(wildcard)) trustedOriginsList.push(wildcard);
7048
- }
7049
- if (project.hostname) {
7050
- const bareHost = project.hostname.replace(/^https?:\/\//, "");
7051
- if (bareHost.endsWith(".localhost") || bareHost === "localhost") {
7052
- trustedOriginsList.push(`http://${bareHost}`);
7053
- trustedOriginsList.push(`http://${bareHost}:*`);
7054
- trustedOriginsList.push(`https://${bareHost}:*`);
7055
- }
7056
- }
7057
- const platformSsoEnabled = String(
7058
- process.env.OS_PLATFORM_SSO ?? "true"
7059
- ).toLowerCase() !== "false";
7060
- const cloudBaseUrl = (process.env.OS_CLOUD_URL ?? "").trim().replace(/\/+$/, "");
7061
- const oidcProviders = platformSsoEnabled && cloudBaseUrl && /^https?:\/\//.test(cloudBaseUrl) ? [{
7062
- providerId: PLATFORM_SSO_PROVIDER_ID,
7063
- name: "ObjectStack",
7064
- discoveryUrl: `${cloudBaseUrl}/.well-known/openid-configuration`,
7065
- clientId: derivePlatformSsoClientId(environmentId),
7066
- clientSecret: derivePlatformSsoClientSecret(this.authBaseSecret, environmentId),
7067
- scopes: ["openid", "email", "profile"]
7068
- }] : void 0;
7069
- await kernel.use(new AuthPlugin({
7070
- secret: projectSecret,
7071
- baseUrl,
7072
- // Project kernel has no http-server (host owns it). The
7073
- // dispatcher's handleAuth path resolves `auth` via
7074
- // getService and invokes the handler directly — route
7075
- // registration is unnecessary and would warn.
7076
- registerRoutes: false,
7077
- // Identity tables live in the project's own DB — keep
7078
- // sys_user/sys_session local to this kernel.
7079
- manifestDatasource: "default",
7080
- // Cookie scope: default to the project's own host. We
7081
- // intentionally do NOT pass crossSubDomainCookies here
7082
- // so cookies stay isolated per project subdomain.
7083
- trustedOrigins: trustedOriginsList.length ? trustedOriginsList : void 0,
7084
- ...oidcProviders ? { oidcProviders } : {}
7085
- }));
7086
- if (oidcProviders) {
7087
- this.logger.info?.("[ArtifactKernelFactory] platform SSO wired", {
7088
- environmentId,
7089
- cloudBaseUrl
7090
- });
7091
- }
7092
- } catch (err) {
7093
- this.logger.warn?.("[ArtifactKernelFactory] AuthPlugin not registered", {
7094
- environmentId,
7095
- error: err?.message
7096
- });
7097
- }
7098
- } else {
7099
- this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { environmentId });
7100
- }
7101
- try {
7102
- const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
7103
- if (multiTenant) {
7104
- try {
7105
- const { OrgScopingPlugin } = await import("@objectstack/plugin-org-scoping");
7106
- await kernel.use(new OrgScopingPlugin());
7107
- } catch (err) {
7108
- this.logger.warn?.("[ArtifactKernelFactory] OrgScopingPlugin not registered (multi-tenant disabled)", {
7109
- environmentId,
7110
- error: err?.message
7111
- });
7112
- }
7113
- }
7114
- const { SecurityPlugin } = await import("@objectstack/plugin-security");
7115
- await kernel.use(new SecurityPlugin());
7116
- } catch (err) {
7117
- this.logger.warn?.("[ArtifactKernelFactory] SecurityPlugin not registered", {
7118
- environmentId,
7119
- error: err?.message
7120
- });
7121
- }
7122
- const projectName = project.hostname ?? environmentId;
7123
- const artifactAny = artifact;
7124
- const topLevelManifest = artifactAny?.manifest && typeof artifactAny.manifest === "object" ? artifactAny.manifest : null;
7125
- const topLevelFunctions = Array.isArray(artifactAny?.functions) ? artifactAny.functions : [];
7126
- const bundle = {
7127
- ...artifact.metadata ?? {},
7128
- ...topLevelManifest ? { manifest: topLevelManifest } : {},
7129
- functions: topLevelFunctions
7130
- };
7131
- const sys = bundle.manifest ?? bundle;
7132
- const packageId = sys?.packageId ?? sys?.package_id ?? bundle?.packageId;
7133
- const i18nCfg = bundle?.i18n ?? sys?.i18n ?? {};
7134
- const trArr = Array.isArray(bundle?.translations) ? bundle.translations : Array.isArray(sys?.translations) ? sys.translations : [];
7135
- try {
7136
- const { I18nServicePlugin } = await import("@objectstack/service-i18n");
7137
- await kernel.use(new I18nServicePlugin({
7138
- defaultLocale: i18nCfg.defaultLocale,
7139
- fallbackLocale: i18nCfg.fallbackLocale ?? i18nCfg.defaultLocale ?? "en",
7140
- // Routes are dispatched by HttpDispatcher.handleI18n via
7141
- // kernel.getService('i18n'); the host worker owns the
7142
- // HTTP server. Skip self-registration to avoid warnings.
7143
- registerRoutes: false
7144
- }));
7145
- console.warn(
7146
- `[ArtifactKernelFactory] I18nServicePlugin registered (project=${environmentId}, translations=${trArr.length}, defaultLocale=${i18nCfg.defaultLocale ?? "en"})`
7147
- );
7148
- } catch (err) {
7149
- this.logger.warn?.("[ArtifactKernelFactory] I18nServicePlugin not registered", {
7150
- environmentId,
7151
- error: err?.message
7152
- });
7153
- }
7154
- const requiresRaw = (Array.isArray(bundle?.requires) ? bundle.requires : null) ?? (Array.isArray(sys?.requires) ? sys.requires : null) ?? [];
7155
- const requires = [
7156
- ...requiresRaw,
7157
- ...this.defaultRequires
7158
- ].filter((x) => typeof x === "string" && x.length > 0);
7159
- if (requires.length > 0) {
7160
- const installed = await loadCapabilities({
7161
- kernel,
7162
- requires,
7163
- bundle: { ...bundle ?? {}, ...sys ?? {} },
7164
- logger: this.logger,
7165
- environmentId
7166
- });
7167
- this.logger.info?.("[ArtifactKernelFactory] capabilities loaded", {
7168
- environmentId,
7169
- requires,
7170
- installed
7171
- });
7172
- }
7173
- await kernel.use(new AppPlugin(bundle, {
7174
- environmentId,
7175
- organizationId: project.organization_id ?? "",
7176
- projectName,
7177
- packageId,
7178
- source: packageId ? "package" : "user"
7179
- }));
7180
- await kernel.bootstrap();
7181
- try {
7182
- const projMeta = typeof project?.metadata === "string" ? JSON.parse(project.metadata) : project?.metadata ?? {};
7183
- const ownerSeed = projMeta?.ownerSeed;
7184
- const orgSeed = projMeta?.orgSeed;
7185
- if (orgSeed?.id && orgSeed?.name) {
7186
- try {
7187
- const { seedProjectOrganization: seedProjectOrganization2 } = await Promise.resolve().then(() => (init_environment_org_seed(), environment_org_seed_exports));
7188
- await seedProjectOrganization2(kernel, orgSeed, this.logger);
7189
- } catch (e) {
7190
- this.logger.warn?.("[ArtifactKernelFactory] orgSeed threw", {
7191
- environmentId,
7192
- error: e?.message
7193
- });
7194
- }
7195
- }
7196
- if (ownerSeed?.userId && ownerSeed?.email) {
7197
- try {
7198
- const { seedProjectOwner: seedProjectOwner2 } = await Promise.resolve().then(() => (init_environment_owner_seed(), environment_owner_seed_exports));
7199
- await seedProjectOwner2(kernel, ownerSeed, this.logger);
7200
- } catch (e) {
7201
- this.logger.warn?.("[ArtifactKernelFactory] ownerSeed threw", {
7202
- environmentId,
7203
- error: e?.message
7204
- });
7205
- }
7206
- if (orgSeed?.id) {
7207
- try {
7208
- const { seedProjectMember: seedProjectMember2 } = await Promise.resolve().then(() => (init_environment_org_seed(), environment_org_seed_exports));
7209
- await seedProjectMember2(
7210
- kernel,
7211
- { userId: ownerSeed.userId, organizationId: orgSeed.id, role: "owner" },
7212
- this.logger
7213
- );
7214
- } catch (e) {
7215
- this.logger.warn?.("[ArtifactKernelFactory] memberSeed threw", {
7216
- environmentId,
7217
- error: e?.message
7218
- });
7219
- }
7220
- }
7221
- }
7222
- } catch (err) {
7223
- this.logger.warn?.("[ArtifactKernelFactory] owner/org seed skipped", {
7224
- environmentId,
7225
- error: err?.message
7226
- });
7227
- }
7228
- try {
7229
- const datasetsNow = (() => {
7230
- try {
7231
- return kernel.getService?.("seed-datasets");
7232
- } catch {
7233
- return void 0;
7234
- }
7235
- })();
7236
- const replayer = (() => {
7237
- try {
7238
- return kernel.getService?.("seed-replayer");
7239
- } catch {
7240
- return void 0;
7241
- }
7242
- })();
7243
- if (Array.isArray(datasetsNow) && datasetsNow.length > 0 && typeof replayer === "function") {
7244
- const projMetaRaw = project?.metadata;
7245
- const projMeta = typeof projMetaRaw === "string" ? (() => {
7246
- try {
7247
- return JSON.parse(projMetaRaw);
7248
- } catch {
7249
- return {};
7250
- }
7251
- })() : projMetaRaw ?? {};
7252
- let primaryOrgId = projMeta?.orgSeed?.id;
7253
- if (!primaryOrgId) {
7254
- try {
7255
- const ql = kernel.getService?.("objectql");
7256
- if (ql?.find) {
7257
- const rows = await ql.find("sys_organization", { limit: 5, orderBy: [{ field: "created_at", direction: "asc" }] });
7258
- const list = Array.isArray(rows) ? rows : rows?.value ?? rows?.records ?? [];
7259
- if (Array.isArray(list) && list.length > 0 && list[0]?.id) {
7260
- primaryOrgId = String(list[0].id);
7261
- }
7262
- }
7263
- } catch {
7264
- }
7265
- }
7266
- if (primaryOrgId) {
7267
- try {
7268
- const summary = await replayer(primaryOrgId);
7269
- const inserted = summary?.inserted ?? 0;
7270
- const updated = summary?.updated ?? 0;
7271
- const errs = summary?.errors?.length ?? 0;
7272
- if (inserted > 0 || updated > 0 || errs > 0) {
7273
- this.logger.info?.("[ArtifactKernelFactory] post-bootstrap seed replay", {
7274
- environmentId,
7275
- organizationId: primaryOrgId,
7276
- datasets: datasetsNow.length,
7277
- inserted,
7278
- updated,
7279
- errors: errs
7280
- });
7281
- }
7282
- } catch (e) {
7283
- this.logger.warn?.("[ArtifactKernelFactory] post-bootstrap seed replay failed", {
7284
- environmentId,
7285
- organizationId: primaryOrgId,
7286
- error: e?.message
7287
- });
7288
- }
7289
- }
7290
- }
7291
- } catch (err) {
7292
- this.logger.warn?.("[ArtifactKernelFactory] post-bootstrap seed step threw", {
7293
- environmentId,
7294
- error: err?.message
7295
- });
7296
- }
7297
- let i18nSvc = null;
7298
- try {
7299
- i18nSvc = kernel.getService?.("i18n");
7300
- } catch {
7301
- i18nSvc = null;
7302
- }
7303
- try {
7304
- if (i18nSvc && typeof i18nSvc.loadTranslations === "function") {
7305
- if (i18nCfg.defaultLocale && typeof i18nSvc.setDefaultLocale === "function") {
7306
- i18nSvc.setDefaultLocale(i18nCfg.defaultLocale);
7307
- }
7308
- let loaded = 0;
7309
- for (const tbundle of trArr) {
7310
- if (!tbundle || typeof tbundle !== "object") continue;
7311
- for (const [locale, data] of Object.entries(tbundle)) {
7312
- if (data && typeof data === "object") {
7313
- try {
7314
- i18nSvc.loadTranslations(locale, data);
7315
- loaded++;
7316
- } catch (err) {
7317
- this.logger.warn?.("[ArtifactKernelFactory] i18n loadTranslations failed", {
7318
- environmentId,
7319
- locale,
7320
- error: err?.message
7321
- });
7322
- }
7323
- }
7324
- }
7325
- }
7326
- if (loaded > 0) {
7327
- this.logger.info?.("[ArtifactKernelFactory] i18n direct-load complete", {
7328
- environmentId,
7329
- locales: loaded,
7330
- bundles: trArr.length
7331
- });
7332
- }
7333
- }
7334
- } catch (err) {
7335
- this.logger.warn?.("[ArtifactKernelFactory] i18n direct-load failed", {
7336
- environmentId,
7337
- error: err?.message
7338
- });
7339
- }
7340
- this.logger.info?.("[ArtifactKernelFactory] kernel ready", {
7341
- environmentId,
7342
- commitId: artifact.commitId,
7343
- checksum: artifact.checksum,
7344
- authEnabled: Boolean(this.authBaseSecret)
7345
- });
7346
- return kernel;
7347
- }
7348
- };
7349
-
7350
- // src/cloud/auth-proxy-plugin.ts
7351
- var import_node_crypto3 = require("crypto");
7352
- var AUTH_PREFIX = "/api/v1/auth";
7353
- function signSessionCookieValue(rawToken, secret) {
7354
- const signature = (0, import_node_crypto3.createHmac)("sha256", secret).update(rawToken).digest("base64");
7355
- return encodeURIComponent(`${rawToken}.${signature}`);
7356
- }
7357
- function buildSetCookieHeader(name, encodedValue, attrs, maxAgeSec) {
7358
- const parts = [`${name}=${encodedValue}`];
7359
- const a = attrs ?? {};
7360
- if (a.path) parts.push(`Path=${a.path}`);
7361
- else parts.push("Path=/");
7362
- if (Number.isFinite(maxAgeSec) && maxAgeSec > 0) parts.push(`Max-Age=${Math.floor(maxAgeSec)}`);
7363
- if (a.domain) parts.push(`Domain=${a.domain}`);
7364
- if (a.sameSite) {
7365
- const ss = String(a.sameSite);
7366
- parts.push(`SameSite=${ss.charAt(0).toUpperCase() + ss.slice(1)}`);
7367
- } else {
7368
- parts.push("SameSite=Lax");
7369
- }
7370
- if (a.secure) parts.push("Secure");
7371
- if (a.httpOnly !== false) parts.push("HttpOnly");
7372
- if (a.partitioned) parts.push("Partitioned");
7373
- return parts.join("; ");
7374
- }
7375
- function pickHandler(svc) {
7376
- if (!svc) return void 0;
7377
- if (typeof svc.handleRequest === "function") return svc.handleRequest.bind(svc);
7378
- if (typeof svc.handler === "function") return svc.handler.bind(svc);
7379
- if (svc.api && typeof svc.api.handler === "function") return svc.api.handler.bind(svc.api);
7380
- if (svc.auth && typeof svc.auth.handler === "function") return svc.auth.handler.bind(svc.auth);
7381
- return void 0;
7382
- }
7383
- async function resolveAuthHandler(svc) {
7384
- const direct = pickHandler(svc);
7385
- if (direct) return direct;
7386
- if (typeof svc?.getApi === "function") {
7387
- try {
7388
- const api = await svc.getApi();
7389
- return pickHandler(api) ?? pickHandler({ api });
7390
- } catch {
7391
- return void 0;
7392
- }
7393
- }
7394
- return void 0;
7395
- }
7396
- var AuthProxyPlugin = class {
7397
- constructor() {
7398
- this.name = "com.objectstack.runtime.auth-proxy";
7399
- this.version = "1.0.0";
7400
- this.init = async (_ctx) => {
7401
- };
7402
- this.start = async (ctx) => {
7403
- ctx.hook("kernel:ready", async () => {
7404
- let httpServer;
7405
- try {
7406
- httpServer = ctx.getService("http-server");
7407
- } catch {
7408
- ctx.logger?.warn?.("[AuthProxyPlugin] http-server not available \u2014 auth routes not mounted");
7409
- return;
7410
- }
7411
- if (!httpServer || typeof httpServer.getRawApp !== "function") {
7412
- ctx.logger?.warn?.("[AuthProxyPlugin] http-server missing getRawApp() \u2014 auth routes not mounted");
7413
- return;
7414
- }
7415
- const rawApp = httpServer.getRawApp();
7416
- const kernelManager = ctx.getService("kernel-manager");
7417
- const envRegistry = ctx.getService("env-registry");
7418
- const handler = async (c) => {
7419
- try {
7420
- const url = new URL(c.req.url);
7421
- const host = url.hostname;
7422
- let environmentId;
7423
- try {
7424
- const env = await envRegistry.resolveByHostname(host);
7425
- environmentId = env?.environmentId;
7426
- } catch {
7427
- }
7428
- if (!environmentId) {
7429
- return c.json({ error: "project_not_found", host }, 404);
7430
- }
7431
- const projectKernel = await kernelManager.getOrCreate(environmentId);
7432
- let authSvc;
7433
- try {
7434
- authSvc = await projectKernel.getServiceAsync?.("auth");
7435
- } catch {
7436
- authSvc = void 0;
7437
- }
7438
- if (!authSvc) {
7439
- try {
7440
- authSvc = projectKernel.getService?.("auth");
7441
- } catch {
7442
- }
7443
- }
7444
- const subPath = url.pathname.startsWith(AUTH_PREFIX + "/") ? url.pathname.substring(AUTH_PREFIX.length + 1) : "";
7445
- if (c.req.method === "GET" && (subPath === "config" || subPath === "bootstrap-status")) {
7446
- if (subPath === "config") {
7447
- try {
7448
- const config = typeof authSvc?.getPublicConfig === "function" ? authSvc.getPublicConfig() : null;
7449
- if (config) {
7450
- return c.json({ success: true, data: config });
7451
- }
7452
- return c.json({ success: false, error: { code: "auth_config_unavailable", message: "AuthManager has no getPublicConfig()" } }, 503);
7453
- } catch (e) {
7454
- return c.json({ success: false, error: { code: "auth_config_error", message: String(e?.message ?? e) } }, 500);
7455
- }
7456
- }
7457
- try {
7458
- const dataEngine = typeof authSvc?.getDataEngine === "function" ? authSvc.getDataEngine() : null;
7459
- if (!dataEngine || typeof dataEngine.count !== "function") {
7460
- return c.json({ hasOwner: true });
7461
- }
7462
- const count = await dataEngine.count("sys_user", {});
7463
- return c.json({ hasOwner: (count ?? 0) > 0 });
7464
- } catch {
7465
- return c.json({ hasOwner: true });
7466
- }
7467
- }
7468
- if (c.req.method === "POST" && subPath === "sso-handoff-issue") {
7469
- try {
7470
- const expected = (process.env.OS_CLOUD_API_KEY ?? "").trim();
7471
- if (!expected) {
7472
- return c.json({ error: "sso_handoff_disabled", reason: "OS_CLOUD_API_KEY unset on env runtime" }, 503);
7473
- }
7474
- const authz = c.req.header("authorization") ?? "";
7475
- const provided = authz.toLowerCase().startsWith("bearer ") ? authz.slice(7).trim() : "";
7476
- if (!provided || provided !== expected) {
7477
- return c.json({ error: "unauthorized" }, 401);
7478
- }
7479
- if (typeof authSvc?.getAuthContext !== "function") {
7480
- return c.json({ error: "auth_service_unavailable" }, 503);
7481
- }
7482
- const handoffAuthCtx = await authSvc.getAuthContext();
7483
- const internal = handoffAuthCtx?.internalAdapter;
7484
- if (!internal?.createVerificationValue) {
7485
- return c.json({ error: "verification_api_unavailable" }, 503);
7486
- }
7487
- let body = {};
7488
- try {
7489
- body = await c.req.json();
7490
- } catch {
7491
- body = {};
7492
- }
7493
- const email = String(body?.email ?? "").toLowerCase().trim();
7494
- if (!email) return c.json({ error: "email_required" }, 400);
7495
- const name = body?.name == null ? null : String(body.name);
7496
- const by = body?.by == null ? "service" : String(body.by);
7497
- const envIdInBody = body?.envId == null ? null : String(body.envId);
7498
- const handoff = (0, import_node_crypto3.randomUUID)().replace(/-/g, "") + (0, import_node_crypto3.randomUUID)().replace(/-/g, "");
7499
- const ttlSec = 60;
7500
- const expiresAt = new Date(Date.now() + ttlSec * 1e3);
7501
- await internal.createVerificationValue({
7502
- identifier: `sso-handoff:${handoff}`,
7503
- value: JSON.stringify({ email, name, by, envId: envIdInBody ?? environmentId }),
7504
- expiresAt
7505
- });
7506
- return c.json({
7507
- token: handoff,
7508
- expiresAt: expiresAt.toISOString(),
7509
- ttlSec
7510
- });
7511
- } catch (err) {
7512
- ctx.logger?.error?.("[AuthProxyPlugin] sso-handoff-issue failed", err instanceof Error ? err : new Error(String(err)));
7513
- return c.json({ error: "sso_handoff_issue_failed", message: String(err?.message ?? err) }, 500);
7514
- }
7515
- }
7516
- if (c.req.method === "GET" && subPath === "sso-exchange") {
7517
- try {
7518
- const token = (url.searchParams.get("token") ?? "").trim();
7519
- const nextRaw = url.searchParams.get("next") ?? "/";
7520
- const next = nextRaw.startsWith("/") ? nextRaw : "/";
7521
- if (!token) return c.text("missing token", 400);
7522
- if (typeof authSvc?.getAuthContext !== "function") {
7523
- return c.text("auth service unavailable", 503);
7524
- }
7525
- const authCtx = await authSvc.getAuthContext();
7526
- const internal = authCtx?.internalAdapter;
7527
- if (!internal?.consumeVerificationValue) {
7528
- return c.text("verification API unavailable", 503);
7529
- }
7530
- const consumed = await internal.consumeVerificationValue(`sso-handoff:${token}`);
7531
- if (!consumed) return c.text("invalid or expired token", 401);
7532
- const expiresAt = consumed?.expiresAt ? new Date(consumed.expiresAt).getTime() : 0;
7533
- if (!expiresAt || expiresAt < Date.now()) return c.text("expired token", 401);
7534
- let payload = {};
7535
- try {
7536
- payload = JSON.parse(String(consumed.value));
7537
- } catch {
7538
- payload = { email: String(consumed.value) };
7539
- }
7540
- const email = String(payload.email ?? "").toLowerCase().trim();
7541
- if (!email) return c.text("handoff missing email", 400);
7542
- const found = await internal.findUserByEmail(email, { includeAccounts: true });
7543
- let userId = found?.user?.id;
7544
- let hasCredentialAccount = (found?.accounts ?? []).some((a) => a.providerId === "credential" && a.password);
7545
- if (!userId) {
7546
- const created = await internal.createUser({
7547
- email,
7548
- name: payload.name ?? email,
7549
- emailVerified: true
7550
- });
7551
- userId = created?.id;
7552
- hasCredentialAccount = false;
7553
- }
7554
- if (!userId) return c.text("failed to provision user", 500);
7555
- const session = await internal.createSession(userId, false);
7556
- const rawToken = session?.token;
7557
- const sessionExpiresAt = session?.expiresAt ? new Date(session.expiresAt) : new Date(Date.now() + 7 * 24 * 3600 * 1e3);
7558
- if (!rawToken) return c.text("failed to mint session", 500);
7559
- const secret = authCtx?.secret ?? "";
7560
- if (!secret) return c.text("auth secret unavailable", 503);
7561
- const cookieName = authCtx?.authCookies?.sessionToken?.name ?? "better-auth.session_token";
7562
- const cookieAttrs = authCtx?.authCookies?.sessionToken?.attributes ?? {};
7563
- const encoded = signSessionCookieValue(rawToken, secret);
7564
- const maxAgeSec = Math.max(60, Math.floor((sessionExpiresAt.getTime() - Date.now()) / 1e3));
7565
- const setCookie = buildSetCookieHeader(cookieName, encoded, cookieAttrs, maxAgeSec);
7566
- const finalNext = hasCredentialAccount ? next : `/_console/system/profile?recovery_needed=true&next=${encodeURIComponent(next)}`;
7567
- const headers = new Headers();
7568
- headers.set("Set-Cookie", setCookie);
7569
- headers.set("Location", finalNext);
7570
- headers.set("Cache-Control", "no-store");
7571
- return new Response(null, { status: 302, headers });
7572
- } catch (err) {
7573
- ctx.logger?.error?.("[AuthProxyPlugin] sso-exchange failed", err instanceof Error ? err : new Error(String(err)));
7574
- return c.text(`sso-exchange failed: ${err?.message ?? String(err)}`, 500);
7575
- }
7576
- }
7577
- if (c.req.method === "POST" && subPath === "set-initial-password") {
7578
- try {
7579
- let body = {};
7580
- try {
7581
- body = await c.req.json();
7582
- } catch {
7583
- body = {};
7584
- }
7585
- const newPassword = body?.newPassword;
7586
- if (typeof newPassword !== "string" || newPassword.length === 0) {
7587
- return c.json({ success: false, error: { code: "invalid_request", message: "newPassword is required" } }, 400);
7588
- }
7589
- if (typeof authSvc?.getAuthContext !== "function") {
7590
- return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
7591
- }
7592
- let userId;
7593
- try {
7594
- const api = typeof authSvc.getApi === "function" ? await authSvc.getApi() : null;
7595
- const session = await api?.getSession?.({ headers: c.req.raw.headers });
7596
- userId = session?.user?.id ? String(session.user.id) : void 0;
7597
- } catch {
7598
- }
7599
- if (!userId) {
7600
- return c.json({ success: false, error: { code: "unauthorized", message: "Sign in first" } }, 401);
7601
- }
7602
- const setPwCtx = await authSvc.getAuthContext();
7603
- if (!setPwCtx?.internalAdapter || !setPwCtx?.password) {
7604
- return c.json({ success: false, error: { code: "unavailable", message: "Auth context unavailable" } }, 503);
7605
- }
7606
- const minLen = setPwCtx.password?.config?.minPasswordLength ?? 8;
7607
- const maxLen = setPwCtx.password?.config?.maxPasswordLength ?? 128;
7608
- if (newPassword.length < minLen) {
7609
- return c.json({ success: false, error: { code: "password_too_short", message: `Password must be at least ${minLen} characters` } }, 400);
7610
- }
7611
- if (newPassword.length > maxLen) {
7612
- return c.json({ success: false, error: { code: "password_too_long", message: `Password must be at most ${maxLen} characters` } }, 400);
7613
- }
7614
- const accounts = await setPwCtx.internalAdapter.findAccounts(userId);
7615
- const existingCredential = accounts?.find?.((a) => a.providerId === "credential" && a.password);
7616
- if (existingCredential) {
7617
- return c.json({ success: false, error: { code: "credential_account_exists", message: "A local password is already set for this account. Use change-password instead." } }, 409);
7618
- }
7619
- const passwordHash = await setPwCtx.password.hash(newPassword);
7620
- await setPwCtx.internalAdapter.createAccount({
7621
- userId,
7622
- providerId: "credential",
7623
- accountId: userId,
7624
- password: passwordHash
7625
- });
7626
- return c.json({ success: true });
7627
- } catch (err) {
7628
- ctx.logger?.error?.("[AuthProxyPlugin] set-initial-password failed", err instanceof Error ? err : new Error(String(err)));
7629
- return c.json({ success: false, error: { code: "set_password_failed", message: String(err?.message ?? err) } }, 500);
7630
- }
7631
- }
7632
- const fn = await resolveAuthHandler(authSvc);
7633
- if (!fn) {
7634
- return c.json({ error: "auth_service_unavailable", environmentId }, 503);
7635
- }
7636
- const resp = await fn(c.req.raw);
7637
- const rootDomain = process.env.OS_ROOT_DOMAIN || "";
7638
- if (rootDomain) {
7639
- const leakyDomain = rootDomain.startsWith(".") ? rootDomain : `.${rootDomain}`;
7640
- const leakyNames = [
7641
- "__Secure-better-auth.session_token",
7642
- "better-auth.session_token",
7643
- "__Secure-better-auth.state",
7644
- "better-auth.state",
7645
- "__Secure-better-auth.csrf_token",
7646
- "better-auth.csrf_token"
7647
- ];
7648
- try {
7649
- for (const n of leakyNames) {
7650
- const isSecure = n.startsWith("__Secure-");
7651
- const attrs = `Max-Age=0; Path=/; Domain=${leakyDomain}; SameSite=Lax${isSecure ? "; Secure" : ""}`;
7652
- resp.headers?.append?.("Set-Cookie", `${n}=; ${attrs}`);
7653
- }
7654
- } catch {
7655
- }
7656
- }
7657
- return resp;
7658
- } catch (err) {
7659
- ctx.logger?.error?.("[AuthProxyPlugin] auth dispatch failed", {
7660
- error: err?.message,
7661
- stack: err?.stack
7662
- });
7663
- return c.json({
7664
- error: "auth_dispatch_failed",
7665
- message: err?.message ?? String(err)
7666
- }, 500);
7667
- }
7668
- };
7669
- if (typeof rawApp.all === "function") {
7670
- rawApp.all(`${AUTH_PREFIX}/*`, handler);
7671
- } else {
7672
- for (const m of ["get", "post", "put", "delete", "patch", "options"]) {
7673
- try {
7674
- rawApp[m]?.(`${AUTH_PREFIX}/*`, handler);
7675
- } catch {
7676
- }
7677
- }
7678
- }
7679
- ctx.logger?.info?.(`[AuthProxyPlugin] auth proxy mounted at ${AUTH_PREFIX}/*`);
7680
- });
7681
- };
7682
- }
7683
- };
7684
-
7685
- // src/cloud/cloud-url.ts
7686
- var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
7687
- function resolveCloudUrl(explicit) {
7688
- const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
7689
- const lower = raw.toLowerCase();
7690
- if (lower === "off" || lower === "none" || lower === "local" || lower === "disabled") {
7691
- return "";
7692
- }
7693
- const picked = raw || DEFAULT_CLOUD_URL;
7694
- return picked.replace(/\/+$/, "");
7695
- }
7696
-
7697
- // src/cloud/marketplace-public-url.ts
7698
- function resolveMarketplacePublicBaseUrl(explicit) {
7699
- const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
7700
- const lower = raw.toLowerCase();
7701
- if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
7702
- return "";
7703
- }
7704
- return raw.replace(/\/+$/, "");
7705
- }
7706
- function publicMarketplaceKeyForApiPath(pathname) {
7707
- const prefix = "/api/v1/marketplace/packages";
7708
- if (pathname === prefix) return "packages.json";
7709
- if (!pathname.startsWith(`${prefix}/`)) return null;
7710
- const tail = pathname.slice(prefix.length + 1);
7711
- if (!tail) return null;
7712
- const parts = tail.split("/");
7713
- if (parts.length === 1) {
7714
- const id = decodeURIComponent(parts[0] ?? "");
7715
- if (!id) return null;
7716
- return `packages/${encodeURIComponent(id)}.json`;
7717
- }
7718
- if (parts.length === 4 && parts[1] === "versions" && parts[3] === "manifest") {
7719
- const id = decodeURIComponent(parts[0] ?? "");
7720
- const versionId = decodeURIComponent(parts[2] ?? "");
7721
- if (!id || !versionId) return null;
7722
- return `packages/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionId)}/manifest.json`;
7723
- }
7724
- return null;
7725
- }
7726
-
7727
- // src/cloud/marketplace-proxy-plugin.ts
7728
- var MARKETPLACE_PREFIX = "/api/v1/marketplace";
7729
- var DEFAULT_LRU_MAX = 200;
7730
- var LIST_TTL_MS = 30 * 60 * 1e3;
7731
- var PACKAGE_TTL_MS = 2 * 60 * 60 * 1e3;
7732
- var VERSION_TTL_MS = 24 * 60 * 60 * 1e3;
7733
- function ttlForPath(pathname) {
7734
- if (/\/packages\/[^/]+\/versions\//.test(pathname)) return VERSION_TTL_MS;
7735
- if (/\/packages\/[^/]+/.test(pathname)) return PACKAGE_TTL_MS;
7736
- return LIST_TTL_MS;
7737
- }
7738
- var LruTtlCache = class {
7739
- constructor(max) {
7740
- this.max = max;
7741
- this.map = /* @__PURE__ */ new Map();
6316
+ // src/cloud/marketplace-proxy-plugin.ts
6317
+ var MARKETPLACE_PREFIX = "/api/v1/marketplace";
6318
+ var DEFAULT_LRU_MAX = 200;
6319
+ var LIST_TTL_MS = 30 * 60 * 1e3;
6320
+ var PACKAGE_TTL_MS = 2 * 60 * 60 * 1e3;
6321
+ var VERSION_TTL_MS = 24 * 60 * 60 * 1e3;
6322
+ function ttlForPath(pathname) {
6323
+ if (/\/packages\/[^/]+\/versions\//.test(pathname)) return VERSION_TTL_MS;
6324
+ if (/\/packages\/[^/]+/.test(pathname)) return PACKAGE_TTL_MS;
6325
+ return LIST_TTL_MS;
6326
+ }
6327
+ var LruTtlCache = class {
6328
+ constructor(max) {
6329
+ this.max = max;
6330
+ this.map = /* @__PURE__ */ new Map();
7742
6331
  }
7743
6332
  get(key) {
7744
6333
  const entry = this.map.get(key);
@@ -8015,359 +6604,10 @@ async function consumeAndMaybeCache(resp, key, pathname, method, cache) {
8015
6604
  return new Response(outBody, { status: resp.status, headers: respHeaders });
8016
6605
  }
8017
6606
 
8018
- // src/cloud/runtime-config-plugin.ts
8019
- var RuntimeConfigPlugin = class {
8020
- constructor(config = {}) {
8021
- this.name = "com.objectstack.runtime.runtime-config";
8022
- this.version = "1.0.0";
8023
- this.init = async (_ctx) => {
8024
- };
8025
- this.start = async (ctx) => {
8026
- ctx.hook("kernel:ready", async () => {
8027
- let httpServer;
8028
- try {
8029
- httpServer = ctx.getService("http-server");
8030
- } catch {
8031
- ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server not available \u2014 runtime/config not mounted");
8032
- return;
8033
- }
8034
- if (!httpServer || typeof httpServer.getRawApp !== "function") {
8035
- ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server missing getRawApp() \u2014 runtime/config not mounted");
8036
- return;
8037
- }
8038
- const rawApp = httpServer.getRawApp();
8039
- const features = {
8040
- installLocal: this.installLocal,
8041
- marketplace: true,
8042
- aiStudio: this.aiStudio
8043
- };
8044
- let envRegistry = null;
8045
- try {
8046
- envRegistry = ctx.getService("env-registry");
8047
- } catch {
8048
- }
8049
- const handler = async (c) => {
8050
- const rawHost = c.req.header("host") ?? "";
8051
- const host = rawHost.split(":")[0].toLowerCase().trim();
8052
- let defaultEnvironmentId;
8053
- let defaultOrgId;
8054
- let resolvedSingleEnv = this.singleEnvironment;
8055
- const resolveFn = typeof envRegistry?.resolveByHostname === "function" ? envRegistry.resolveByHostname.bind(envRegistry) : typeof envRegistry?.resolveHostname === "function" ? envRegistry.resolveHostname.bind(envRegistry) : null;
8056
- if (resolveFn && host) {
8057
- try {
8058
- const resolved = await resolveFn(host);
8059
- if (resolved?.environmentId) {
8060
- defaultEnvironmentId = String(resolved.environmentId);
8061
- const orgId = resolved.organizationId ?? resolved.organization_id;
8062
- if (orgId) defaultOrgId = String(orgId);
8063
- resolvedSingleEnv = true;
8064
- }
8065
- } catch {
8066
- }
8067
- }
8068
- return c.json({
8069
- cloudUrl: this.cloudUrl,
8070
- singleEnvironment: resolvedSingleEnv,
8071
- defaultOrgId,
8072
- defaultEnvironmentId,
8073
- features,
8074
- branding: {
8075
- productName: this.productName,
8076
- productShortName: this.productShortName
8077
- }
8078
- });
8079
- };
8080
- rawApp.get("/api/v1/runtime/config", handler);
8081
- rawApp.get("/api/v1/studio/runtime-config", handler);
8082
- ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
8083
- cloudUrl: this.cloudUrl || "(empty)",
8084
- installLocal: this.installLocal,
8085
- perHostEnvResolution: !!envRegistry
8086
- });
8087
- });
8088
- };
8089
- this.destroy = async () => {
8090
- };
8091
- this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
8092
- this.installLocal = !!config.installLocal;
8093
- this.aiStudio = config.aiStudio !== false;
8094
- this.singleEnvironment = !!config.singleEnvironment;
8095
- const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
8096
- const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
8097
- this.productName = (config.productName ?? envName ?? "ObjectOS").trim() || "ObjectOS";
8098
- this.productShortName = (config.productShortName ?? envShort ?? this.productName).trim() || this.productName;
8099
- }
8100
- };
8101
-
8102
- // src/cloud/file-artifact-api-client.ts
8103
- var import_promises2 = require("fs/promises");
8104
- var import_node_path6 = require("path");
8105
- var FileArtifactApiClient = class {
8106
- constructor(config = {}) {
8107
- const cwd = process.cwd();
8108
- this.artifactPath = (0, import_node_path6.resolve)(
8109
- cwd,
8110
- config.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? "dist/objectstack.json"
8111
- );
8112
- this.environmentId = config.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
8113
- this.organizationId = config.organizationId ?? process.env.OS_ORGANIZATION_ID ?? "org_local";
8114
- this.overrideRuntime = config.runtime;
8115
- this.watch = config.watch ?? true;
8116
- this.logger = config.logger ?? console;
8117
- }
8118
- async resolveHostname(_host) {
8119
- const runtime = this.overrideRuntime ?? await this.readRuntimeFromArtifact();
8120
- return {
8121
- environmentId: this.environmentId,
8122
- organizationId: this.organizationId,
8123
- ...runtime ? { runtime } : {}
8124
- };
8125
- }
8126
- async fetchArtifact(_environmentId, _opts) {
8127
- return this.loadArtifact();
8128
- }
8129
- async lookupProjectByShortId(_shortId) {
8130
- return { environmentId: this.environmentId, organizationId: this.organizationId };
8131
- }
8132
- async fetchBranchHead(_environmentId, _branchName) {
8133
- const artifact = await this.loadArtifact();
8134
- return artifact ? { commitId: artifact.commitId ?? "local", publishedAt: null } : null;
8135
- }
8136
- invalidate(_environmentId) {
8137
- this.cached = void 0;
8138
- }
8139
- clear() {
8140
- this.cached = void 0;
8141
- }
8142
- async loadArtifact() {
8143
- try {
8144
- const stats = await (0, import_promises2.stat)(this.artifactPath);
8145
- const mtimeMs = stats.mtimeMs;
8146
- if (!this.watch && this.cached) return this.cached.response;
8147
- if (this.cached && this.cached.mtimeMs === mtimeMs) return this.cached.response;
8148
- const raw = await (0, import_promises2.readFile)(this.artifactPath, "utf8");
8149
- const parsed = JSON.parse(raw);
8150
- const isEnvelope = parsed && typeof parsed === "object" && typeof parsed.metadata === "object" && parsed.metadata !== null;
8151
- const metadata = isEnvelope ? parsed.metadata : parsed;
8152
- const runtime = this.overrideRuntime ?? (isEnvelope ? parsed.runtime : void 0) ?? this.deriveRuntimeFromMetadata(metadata) ?? this.defaultLocalSqliteRuntime();
8153
- const response = {
8154
- schemaVersion: parsed.schemaVersion ?? "1",
8155
- environmentId: parsed.environmentId ?? this.environmentId,
8156
- commitId: parsed.commitId ?? "local",
8157
- checksum: parsed.checksum ?? "",
8158
- publishedAt: parsed.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
8159
- metadata,
8160
- functions: parsed.functions,
8161
- manifest: parsed.manifest,
8162
- runtime: {
8163
- organizationId: this.organizationId,
8164
- ...runtime
8165
- }
8166
- };
8167
- this.cached = { mtimeMs, response };
8168
- return response;
8169
- } catch (err) {
8170
- this.logger.error?.("[FileArtifactApiClient] failed to load artifact", {
8171
- artifactPath: this.artifactPath,
8172
- error: err?.message ?? err
8173
- });
8174
- return null;
8175
- }
8176
- }
8177
- async readRuntimeFromArtifact() {
8178
- const artifact = await this.loadArtifact();
8179
- return artifact?.runtime;
8180
- }
8181
- deriveRuntimeFromMetadata(metadata) {
8182
- const datasources = metadata?.datasources;
8183
- if (!Array.isArray(datasources) || datasources.length === 0) return void 0;
8184
- const mapping = metadata?.datasourceMapping;
8185
- let preferredName;
8186
- if (mapping) {
8187
- const def = mapping.find((m) => m?.default === true);
8188
- if (def?.datasource) preferredName = def.datasource;
8189
- }
8190
- const ds = preferredName ? datasources.find((d) => d?.name === preferredName) ?? datasources[0] : datasources[0];
8191
- if (!ds || typeof ds !== "object") return void 0;
8192
- const config = ds.config ?? {};
8193
- const url = config.url ?? config.connectionString ?? config.connection ?? config.filename;
8194
- const driver = ds.driver;
8195
- if (typeof driver !== "string" || typeof url !== "string") return void 0;
8196
- return {
8197
- databaseDriver: driver,
8198
- databaseUrl: url,
8199
- databaseAuthToken: typeof config.authToken === "string" ? config.authToken : void 0
8200
- };
8201
- }
8202
- defaultLocalSqliteRuntime() {
8203
- const cwd = process.cwd();
8204
- const dbPath = (0, import_node_path6.resolve)(cwd, ".objectstack/data", `${this.environmentId}.db`);
8205
- return {
8206
- databaseDriver: "sqlite",
8207
- databaseUrl: `file:${dbPath}`
8208
- };
8209
- }
8210
- };
8211
-
8212
- // src/cloud/objectos-stack.ts
8213
- async function createHostEnginePlugins() {
8214
- const { ObjectQLPlugin } = await import("@objectstack/objectql");
8215
- const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
8216
- const { MetadataPlugin } = await import("@objectstack/metadata");
8217
- const { InMemoryDriver } = await import("@objectstack/driver-memory");
8218
- const driver = new InMemoryDriver();
8219
- const driverName = "memory";
8220
- const oqlRef = { ql: null };
8221
- const objectql = {
8222
- name: "com.objectstack.engine.objectql",
8223
- version: "0.0.0",
8224
- async init(ctx) {
8225
- const plugin = new ObjectQLPlugin();
8226
- this._inner = plugin;
8227
- if (plugin.init) await plugin.init(ctx);
8228
- oqlRef.ql = plugin.ql ?? plugin;
8229
- },
8230
- async start(ctx) {
8231
- const plugin = this._inner;
8232
- if (plugin?.start) await plugin.start(ctx);
8233
- },
8234
- async destroy() {
8235
- const plugin = this._inner;
8236
- if (plugin?.destroy) await plugin.destroy();
8237
- else if (plugin?.stop) await plugin.stop();
8238
- }
8239
- };
8240
- const datasourceMapping = {
8241
- name: "objectos-host-datasource-mapping",
8242
- version: "0.0.0",
8243
- dependencies: ["com.objectstack.engine.objectql"],
8244
- async init() {
8245
- const ql = oqlRef.ql;
8246
- if (ql?.setDatasourceMapping) {
8247
- ql.setDatasourceMapping([
8248
- { default: true, datasource: `com.objectstack.driver.${driverName}` }
8249
- ]);
8250
- }
8251
- }
8252
- };
8253
- const driverPlugin = new DriverPlugin2(driver, driverName);
8254
- const metadata = new MetadataPlugin({
8255
- watch: false,
8256
- // The host kernel is a routing shell. It doesn't own metadata —
8257
- // every per-project kernel registers its own.
8258
- registerSystemObjects: false
8259
- });
8260
- return [objectql, datasourceMapping, driverPlugin, metadata];
8261
- }
8262
- var ObjectOSEnvironmentPlugin = class {
8263
- constructor(config) {
8264
- this.name = "com.objectstack.runtime.objectos-environment";
8265
- this.version = "1.0.0";
8266
- this.init = async (ctx) => {
8267
- const client = this.config.client ?? (this.config.controlPlaneUrl === "file" ? new FileArtifactApiClient({
8268
- ...this.config.fileConfig ?? {},
8269
- logger: ctx.logger
8270
- }) : new ArtifactApiClient({
8271
- controlPlaneUrl: this.config.controlPlaneUrl,
8272
- apiKey: this.config.controlPlaneApiKey,
8273
- cacheTtlMs: this.config.artifactCacheTtlMs,
8274
- logger: ctx.logger
8275
- }));
8276
- this.client = client;
8277
- const envRegistry = new ArtifactEnvironmentRegistry({
8278
- client,
8279
- cacheTtlMs: this.config.envCacheTtlMs,
8280
- logger: ctx.logger
8281
- });
8282
- const factory = new ArtifactKernelFactory({
8283
- client,
8284
- envRegistry,
8285
- logger: ctx.logger,
8286
- defaultRequires: this.config.defaultRequires
8287
- });
8288
- const kernelManager = new KernelManager({
8289
- factory,
8290
- maxSize: this.config.kernelCacheSize,
8291
- ttlMs: this.config.kernelTtlMs,
8292
- logger: ctx.logger,
8293
- // Only the HTTP client exposes /freshness; file-mode (CLI dev)
8294
- // has no upstream to probe.
8295
- freshnessProbe: this.config.controlPlaneUrl === "file" ? void 0 : async (envId, builtAtMs) => {
8296
- const fresh = await client.getFreshness(envId);
8297
- if (!fresh) return false;
8298
- const t = fresh.lastPublishedAt ? Date.parse(fresh.lastPublishedAt) : NaN;
8299
- if (!Number.isFinite(t)) return false;
8300
- if (t <= builtAtMs) return false;
8301
- try {
8302
- client.invalidate(envId);
8303
- } catch {
8304
- }
8305
- return true;
8306
- }
8307
- });
8308
- this.kernelManager = kernelManager;
8309
- ctx.registerService("env-registry", envRegistry);
8310
- ctx.registerService("kernel-manager", kernelManager);
8311
- ctx.registerService("artifact-api-client", client);
8312
- ctx.logger.info?.("ObjectOSEnvironmentPlugin: registered env-registry + kernel-manager", {
8313
- mode: this.config.controlPlaneUrl === "file" ? "file" : "http",
8314
- controlPlaneUrl: this.config.controlPlaneUrl
8315
- });
8316
- };
8317
- this.destroy = async () => {
8318
- try {
8319
- await this.kernelManager?.evictAll();
8320
- } catch {
8321
- }
8322
- try {
8323
- this.client?.clear();
8324
- } catch {
8325
- }
8326
- };
8327
- this.config = config;
8328
- }
8329
- };
8330
- async function createObjectOSStack(config) {
8331
- if (!config.controlPlaneUrl && !config.client) {
8332
- throw new Error("[createObjectOSStack] either controlPlaneUrl or client is required");
8333
- }
8334
- const merged = {
8335
- ...config,
8336
- kernelCacheSize: Number(process.env.OS_KERNEL_CACHE_SIZE ?? config.kernelCacheSize ?? 32),
8337
- kernelTtlMs: Number(process.env.OS_KERNEL_TTL_MS ?? config.kernelTtlMs ?? 15 * 60 * 1e3),
8338
- envCacheTtlMs: Number(process.env.OS_ENV_CACHE_TTL_MS ?? config.envCacheTtlMs ?? 5 * 60 * 1e3),
8339
- artifactCacheTtlMs: Number(process.env.OS_ARTIFACT_CACHE_TTL_MS ?? config.artifactCacheTtlMs ?? 5 * 60 * 1e3)
8340
- };
8341
- const enginePlugins = await createHostEnginePlugins();
8342
- return {
8343
- plugins: [
8344
- ...enginePlugins,
8345
- new ObjectOSEnvironmentPlugin(merged),
8346
- new AuthProxyPlugin(),
8347
- new MarketplaceProxyPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl }),
8348
- new RuntimeConfigPlugin({ controlPlaneUrl: merged.controlPlaneUrl === "file" ? void 0 : merged.controlPlaneUrl, installLocal: false }),
8349
- // Host-supplied product/policy plugins (the official seam — see
8350
- // ObjectOSStackConfig.extraPlugins). Appended last so they mount
8351
- // after the framework defaults.
8352
- ...config.extraPlugins ?? []
8353
- ],
8354
- api: {
8355
- enableProjectScoping: true,
8356
- projectResolution: "auto",
8357
- // ObjectOS is multi-tenant: anonymous /api/v1/data/* must never
8358
- // leak per-project data across organisations. AuthProxyPlugin
8359
- // verifies upstream tokens and populates ctx.userId; requireAuth
8360
- // turns missing userId into 401 at the REST layer before the
8361
- // request reaches the per-project kernel.
8362
- requireAuth: true
8363
- }
8364
- };
8365
- }
8366
-
8367
6607
  // src/cloud/marketplace-install-local-plugin.ts
8368
6608
  var import_node_fs4 = require("fs");
8369
- var import_node_path7 = require("path");
8370
- var import_types4 = require("@objectstack/types");
6609
+ var import_node_path5 = require("path");
6610
+ var import_types3 = require("@objectstack/types");
8371
6611
  var ROUTE_BASE = "/api/v1/marketplace/install-local";
8372
6612
  var DEFAULT_DIR = ".objectstack/installed-packages";
8373
6613
  function safeFilename(manifestId) {
@@ -8567,7 +6807,7 @@ var MarketplaceInstallLocalPlugin = class {
8567
6807
  };
8568
6808
  try {
8569
6809
  (0, import_node_fs4.mkdirSync)(this.storageDir, { recursive: true });
8570
- (0, import_node_fs4.writeFileSync)((0, import_node_path7.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
6810
+ (0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
8571
6811
  } catch (err) {
8572
6812
  return c.json({
8573
6813
  success: false,
@@ -8587,7 +6827,7 @@ var MarketplaceInstallLocalPlugin = class {
8587
6827
  if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
8588
6828
  entry.withSampleData = true;
8589
6829
  try {
8590
- (0, import_node_fs4.writeFileSync)((0, import_node_path7.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
6830
+ (0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
8591
6831
  } catch {
8592
6832
  }
8593
6833
  }
@@ -8634,7 +6874,7 @@ var MarketplaceInstallLocalPlugin = class {
8634
6874
  if (!manifestId) {
8635
6875
  return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
8636
6876
  }
8637
- const file = (0, import_node_path7.join)(this.storageDir, safeFilename(manifestId));
6877
+ const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
8638
6878
  if (!(0, import_node_fs4.existsSync)(file)) {
8639
6879
  return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
8640
6880
  }
@@ -8662,7 +6902,7 @@ var MarketplaceInstallLocalPlugin = class {
8662
6902
  * (refuse to avoid silently overwriting authored code)
8663
6903
  */
8664
6904
  this.findConflict = (ctx, manifestId) => {
8665
- if ((0, import_node_fs4.existsSync)((0, import_node_path7.join)(this.storageDir, safeFilename(manifestId)))) {
6905
+ if ((0, import_node_fs4.existsSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)))) {
8666
6906
  return "marketplace";
8667
6907
  }
8668
6908
  try {
@@ -8704,7 +6944,7 @@ var MarketplaceInstallLocalPlugin = class {
8704
6944
  if (!manifestId) {
8705
6945
  return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
8706
6946
  }
8707
- const file = (0, import_node_path7.join)(this.storageDir, safeFilename(manifestId));
6947
+ const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
8708
6948
  if (!(0, import_node_fs4.existsSync)(file)) {
8709
6949
  return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
8710
6950
  }
@@ -8758,7 +6998,7 @@ var MarketplaceInstallLocalPlugin = class {
8758
6998
  if (!manifestId) {
8759
6999
  return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
8760
7000
  }
8761
- const file = (0, import_node_path7.join)(this.storageDir, safeFilename(manifestId));
7001
+ const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
8762
7002
  if (!(0, import_node_fs4.existsSync)(file)) {
8763
7003
  return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
8764
7004
  }
@@ -8911,7 +7151,7 @@ var MarketplaceInstallLocalPlugin = class {
8911
7151
  }
8912
7152
  }
8913
7153
  if (opts.seedNow && datasets.length > 0) {
8914
- const multiTenant = String((0, import_types4.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
7154
+ const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
8915
7155
  try {
8916
7156
  const ql = ctx.getService("objectql");
8917
7157
  let metadata;
@@ -9017,7 +7257,7 @@ var MarketplaceInstallLocalPlugin = class {
9017
7257
  for (const name of (0, import_node_fs4.readdirSync)(this.storageDir)) {
9018
7258
  if (!name.endsWith(".json")) continue;
9019
7259
  try {
9020
- const raw = (0, import_node_fs4.readFileSync)((0, import_node_path7.join)(this.storageDir, name), "utf8");
7260
+ const raw = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(this.storageDir, name), "utf8");
9021
7261
  out.push(JSON.parse(raw));
9022
7262
  } catch {
9023
7263
  }
@@ -9025,7 +7265,91 @@ var MarketplaceInstallLocalPlugin = class {
9025
7265
  return out;
9026
7266
  };
9027
7267
  this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
9028
- this.storageDir = config.storageDir ? (0, import_node_path7.resolve)(config.storageDir) : (0, import_node_path7.resolve)(process.cwd(), DEFAULT_DIR);
7268
+ this.storageDir = config.storageDir ? (0, import_node_path5.resolve)(config.storageDir) : (0, import_node_path5.resolve)(process.cwd(), DEFAULT_DIR);
7269
+ }
7270
+ };
7271
+
7272
+ // src/cloud/runtime-config-plugin.ts
7273
+ var RuntimeConfigPlugin = class {
7274
+ constructor(config = {}) {
7275
+ this.name = "com.objectstack.runtime.runtime-config";
7276
+ this.version = "1.0.0";
7277
+ this.init = async (_ctx) => {
7278
+ };
7279
+ this.start = async (ctx) => {
7280
+ ctx.hook("kernel:ready", async () => {
7281
+ let httpServer;
7282
+ try {
7283
+ httpServer = ctx.getService("http-server");
7284
+ } catch {
7285
+ ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server not available \u2014 runtime/config not mounted");
7286
+ return;
7287
+ }
7288
+ if (!httpServer || typeof httpServer.getRawApp !== "function") {
7289
+ ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server missing getRawApp() \u2014 runtime/config not mounted");
7290
+ return;
7291
+ }
7292
+ const rawApp = httpServer.getRawApp();
7293
+ const features = {
7294
+ installLocal: this.installLocal,
7295
+ marketplace: true,
7296
+ aiStudio: this.aiStudio
7297
+ };
7298
+ let envRegistry = null;
7299
+ try {
7300
+ envRegistry = ctx.getService("env-registry");
7301
+ } catch {
7302
+ }
7303
+ const handler = async (c) => {
7304
+ const rawHost = c.req.header("host") ?? "";
7305
+ const host = rawHost.split(":")[0].toLowerCase().trim();
7306
+ let defaultEnvironmentId;
7307
+ let defaultOrgId;
7308
+ let resolvedSingleEnv = this.singleEnvironment;
7309
+ const resolveFn = typeof envRegistry?.resolveByHostname === "function" ? envRegistry.resolveByHostname.bind(envRegistry) : typeof envRegistry?.resolveHostname === "function" ? envRegistry.resolveHostname.bind(envRegistry) : null;
7310
+ if (resolveFn && host) {
7311
+ try {
7312
+ const resolved = await resolveFn(host);
7313
+ if (resolved?.environmentId) {
7314
+ defaultEnvironmentId = String(resolved.environmentId);
7315
+ const orgId = resolved.organizationId ?? resolved.organization_id;
7316
+ if (orgId) defaultOrgId = String(orgId);
7317
+ resolvedSingleEnv = true;
7318
+ }
7319
+ } catch {
7320
+ }
7321
+ }
7322
+ return c.json({
7323
+ cloudUrl: this.cloudUrl,
7324
+ singleEnvironment: resolvedSingleEnv,
7325
+ defaultOrgId,
7326
+ defaultEnvironmentId,
7327
+ features,
7328
+ branding: {
7329
+ productName: this.productName,
7330
+ productShortName: this.productShortName
7331
+ }
7332
+ });
7333
+ };
7334
+ rawApp.get("/api/v1/runtime/config", handler);
7335
+ rawApp.get("/api/v1/studio/runtime-config", handler);
7336
+ ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
7337
+ cloudUrl: this.cloudUrl || "(empty)",
7338
+ installLocal: this.installLocal,
7339
+ perHostEnvResolution: !!envRegistry
7340
+ });
7341
+ });
7342
+ };
7343
+ this.destroy = async () => {
7344
+ };
7345
+ this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
7346
+ this.installLocal = !!config.installLocal;
7347
+ this.aiStudio = config.aiStudio !== false;
7348
+ this.singleEnvironment = !!config.singleEnvironment;
7349
+ const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
7350
+ const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
7351
+ this.productName = (config.productName ?? envName ?? "ObjectOS").trim() || "ObjectOS";
7352
+ this.productShortName = (config.productShortName ?? envShort ?? this.productName).trim() || this.productName;
9029
7353
  }
9030
7354
  };
9031
7355
 
@@ -9054,24 +7378,18 @@ init_body_runner();
9054
7378
  // src/index.ts
9055
7379
  var import_rest = require("@objectstack/rest");
9056
7380
  __reExport(index_exports, require("@objectstack/core"), module.exports);
9057
- var import_types5 = require("@objectstack/types");
7381
+ var import_types4 = require("@objectstack/types");
9058
7382
  // Annotate the CommonJS export names for ESM import in node:
9059
7383
  0 && (module.exports = {
9060
7384
  AppPlugin,
9061
- ArtifactApiClient,
9062
- ArtifactEnvironmentRegistry,
9063
- ArtifactKernelFactory,
9064
- AuthProxyPlugin,
9065
7385
  DEFAULT_CLOUD_URL,
9066
7386
  DEFAULT_RATE_LIMITS,
9067
7387
  DriverPlugin,
9068
7388
  ExternalValidationPlugin,
9069
- FileArtifactApiClient,
9070
7389
  HttpDispatcher,
9071
7390
  HttpServer,
9072
7391
  InMemoryErrorReporter,
9073
7392
  InMemoryMetricsRegistry,
9074
- KernelManager,
9075
7393
  MarketplaceInstallLocalPlugin,
9076
7394
  MarketplaceProxyPlugin,
9077
7395
  MiddlewareManager,
@@ -9081,7 +7399,6 @@ var import_types5 = require("@objectstack/types");
9081
7399
  OBSERVABILITY_METRICS_SERVICE,
9082
7400
  ObjectKernel,
9083
7401
  ObservabilityServicePlugin,
9084
- PLATFORM_SSO_PROVIDER_ID,
9085
7402
  QuickJSScriptRunner,
9086
7403
  RUNTIME_METRICS,
9087
7404
  RateLimiter,
@@ -9096,8 +7413,6 @@ var import_types5 = require("@objectstack/types");
9096
7413
  UnimplementedScriptRunner,
9097
7414
  _resetEnvDeprecationWarnings,
9098
7415
  actionBodyRunnerFactory,
9099
- backfillPlatformSsoClients,
9100
- buildPlatformSsoRedirectUri,
9101
7416
  buildSecurityHeaders,
9102
7417
  collectBundleActions,
9103
7418
  collectBundleFunctions,
@@ -9105,12 +7420,9 @@ var import_types5 = require("@objectstack/types");
9105
7420
  createDefaultHostConfig,
9106
7421
  createDispatcherPlugin,
9107
7422
  createExternalValidationPlugin,
9108
- createObjectOSStack,
9109
7423
  createRestApiPlugin,
9110
7424
  createStandaloneStack,
9111
7425
  createSystemEnvironmentPlugin,
9112
- derivePlatformSsoClientId,
9113
- derivePlatformSsoClientSecret,
9114
7426
  extractRequestId,
9115
7427
  formatTraceparent,
9116
7428
  generateRequestId,
@@ -9127,7 +7439,6 @@ var import_types5 = require("@objectstack/types");
9127
7439
  resolveMetrics,
9128
7440
  resolveObjectStackHome,
9129
7441
  resolveRequestId,
9130
- seedPlatformSsoClient,
9131
7442
  ...require("@objectstack/core")
9132
7443
  });
9133
7444
  //# sourceMappingURL=index.cjs.map